TargetResponseHelper.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. /* Copyright (C) 2012-2016 Tal Aloni <tal.aloni.il@gmail.com>. All rights reserved.
  2. *
  3. * You can redistribute this program and/or modify it under the terms of
  4. * the GNU Lesser Public License as published by the Free Software Foundation,
  5. * either version 3 of the License, or (at your option) any later version.
  6. */
  7. using System;
  8. using System.Collections.Generic;
  9. using System.IO;
  10. using System.Text;
  11. using System.Runtime.InteropServices;
  12. using DiskAccessLibrary;
  13. using Utilities;
  14. namespace ISCSI.Server
  15. {
  16. public class TargetResponseHelper
  17. {
  18. internal static List<ISCSIPDU> GetReadyToTransferPDUs(SCSICommandPDU command, ISCSITarget target, SessionParameters session, ConnectionParameters connection, out List<SCSICommandPDU> commandsToExecute)
  19. {
  20. // We return either SCSIResponsePDU or List<SCSIDataInPDU>
  21. List<ISCSIPDU> responseList = new List<ISCSIPDU>();
  22. commandsToExecute = new List<SCSICommandPDU>();
  23. string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
  24. if (command.Write && command.DataSegmentLength < command.ExpectedDataTransferLength)
  25. {
  26. uint transferTag = session.GetNextTransferTag();
  27. // Create buffer for next segments (we only execute the command after receiving all of its data)
  28. Array.Resize<byte>(ref command.Data, (int)command.ExpectedDataTransferLength);
  29. // Send R2T
  30. ReadyToTransferPDU response = new ReadyToTransferPDU();
  31. response.InitiatorTaskTag = command.InitiatorTaskTag;
  32. response.R2TSN = 0; // R2Ts are sequenced per command and must start with 0 for each new command;
  33. response.TargetTransferTag = transferTag;
  34. response.BufferOffset = command.DataSegmentLength;
  35. response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);
  36. connection.AddTransfer(transferTag, command, 1);
  37. session.CommandsInTransfer.Add(command.CmdSN);
  38. responseList.Add(response);
  39. return responseList;
  40. }
  41. if (session.IsPrecedingCommandPending(command.CmdSN))
  42. {
  43. session.DelayedCommands.Add(command);
  44. }
  45. else
  46. {
  47. commandsToExecute.Add(command);
  48. }
  49. return responseList;
  50. }
  51. internal static List<ISCSIPDU> GetReadyToTransferPDUs(SCSIDataOutPDU request, ISCSITarget target, SessionParameters session, ConnectionParameters connection, out List<SCSICommandPDU> commandsToExecute)
  52. {
  53. List<ISCSIPDU> responseList = new List<ISCSIPDU>();
  54. commandsToExecute = new List<SCSICommandPDU>();
  55. string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
  56. TransferEntry transfer = connection.GetTransferEntry(request.TargetTransferTag);
  57. if (transfer == null)
  58. {
  59. ISCSIServer.Log("[{0}][GetSCSIDataOutResponsePDU] Invalid TargetTransferTag {1}", connectionIdentifier, request.TargetTransferTag);
  60. RejectPDU reject = new RejectPDU();
  61. reject.InitiatorTaskTag = request.InitiatorTaskTag;
  62. reject.Reason = RejectReason.InvalidPDUField;
  63. reject.Data = ByteReader.ReadBytes(request.GetBytes(), 0, 48);
  64. responseList.Add(reject);
  65. return responseList;
  66. }
  67. ushort LUN = (ushort)request.LUN;
  68. Disk disk = target.Disks[LUN];
  69. uint offset = request.BufferOffset;
  70. uint totalLength = (uint)transfer.Command.ExpectedDataTransferLength;
  71. // Store segment (we only execute the command after receiving all of its data)
  72. Array.Copy(request.Data, 0, transfer.Command.Data, offset, request.DataSegmentLength);
  73. ISCSIServer.Log(String.Format("[{0}][GetSCSIDataOutResponsePDU] Buffer offset: {1}, Total length: {2}", connectionIdentifier, offset, totalLength));
  74. if (offset + request.DataSegmentLength == totalLength)
  75. {
  76. // Last Data-out PDU
  77. if (session.IsPrecedingCommandPending(transfer.Command.CmdSN))
  78. {
  79. session.DelayedCommands.Add(transfer.Command);
  80. }
  81. else
  82. {
  83. commandsToExecute.Add(transfer.Command);
  84. connection.RemoveTransfer(request.TargetTransferTag);
  85. session.CommandsInTransfer.Remove(transfer.Command.CmdSN);
  86. // Check if delayed commands are ready to be executed
  87. List<SCSICommandPDU> pendingCommands = session.GetDelayedCommandsReadyForExecution();
  88. foreach (SCSICommandPDU pendingCommand in pendingCommands)
  89. {
  90. commandsToExecute.Add(pendingCommand);
  91. }
  92. }
  93. return responseList;
  94. }
  95. else
  96. {
  97. // Send R2T
  98. ReadyToTransferPDU response = new ReadyToTransferPDU();
  99. response.InitiatorTaskTag = request.InitiatorTaskTag;
  100. response.TargetTransferTag = request.TargetTransferTag;
  101. response.R2TSN = transfer.NextR2NSN;
  102. response.BufferOffset = offset + request.DataSegmentLength; // where we left off
  103. response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, totalLength - response.BufferOffset);
  104. transfer.NextR2NSN++;
  105. responseList.Add(response);
  106. return responseList;
  107. }
  108. }
  109. internal static List<ISCSIPDU> GetSCSICommandResponse(SCSICommandPDU command, ISCSITarget target, SessionParameters session, ConnectionParameters connection)
  110. {
  111. string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
  112. ISCSIServer.Log("[{0}] Executing Command: CmdSN: {1}", connectionIdentifier, command.CmdSN);
  113. byte[] scsiResponse;
  114. SCSIStatusCodeName status = target.ExecuteCommand(command.CommandDescriptorBlock, command.LUN, command.Data, out scsiResponse);
  115. return PrepareSCSICommandResponse(command, status, scsiResponse, connection);
  116. }
  117. internal static List<ISCSIPDU> PrepareSCSICommandResponse(SCSICommandPDU command, SCSIStatusCodeName status, byte[] scsiResponse, ConnectionParameters connection)
  118. {
  119. List<ISCSIPDU> responseList = new List<ISCSIPDU>();
  120. if (!command.Read || status != SCSIStatusCodeName.Good)
  121. {
  122. // RFC 3720: if the command is completed with an error, then the response and sense data MUST be sent in a SCSI Response PDU
  123. SCSIResponsePDU response = new SCSIResponsePDU();
  124. response.InitiatorTaskTag = command.InitiatorTaskTag;
  125. response.Status = status;
  126. response.Data = scsiResponse;
  127. if (command.Read)
  128. {
  129. EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
  130. }
  131. responseList.Add(response);
  132. }
  133. else if (scsiResponse.Length <= connection.InitiatorMaxRecvDataSegmentLength)
  134. {
  135. SCSIDataInPDU response = new SCSIDataInPDU();
  136. response.InitiatorTaskTag = command.InitiatorTaskTag;
  137. response.Status = status;
  138. response.StatusPresent = true;
  139. response.Final = true;
  140. response.Data = scsiResponse;
  141. EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
  142. responseList.Add(response);
  143. }
  144. else // we have to split the response to multiple Data-In PDUs
  145. {
  146. int bytesLeftToSend = scsiResponse.Length;
  147. uint dataSN = 0;
  148. while (bytesLeftToSend > 0)
  149. {
  150. int dataSegmentLength = Math.Min(connection.InitiatorMaxRecvDataSegmentLength, bytesLeftToSend);
  151. int dataOffset = scsiResponse.Length - bytesLeftToSend;
  152. SCSIDataInPDU response = new SCSIDataInPDU();
  153. response.InitiatorTaskTag = command.InitiatorTaskTag;
  154. if (bytesLeftToSend == dataSegmentLength)
  155. {
  156. // last Data-In PDU
  157. response.Status = status;
  158. response.StatusPresent = true;
  159. response.Final = true;
  160. }
  161. response.BufferOffset = (uint)dataOffset;
  162. response.DataSN = dataSN;
  163. dataSN++;
  164. response.Data = new byte[dataSegmentLength];
  165. Array.Copy(scsiResponse, dataOffset, response.Data, 0, dataSegmentLength);
  166. responseList.Add(response);
  167. bytesLeftToSend -= dataSegmentLength;
  168. }
  169. }
  170. return responseList;
  171. }
  172. public static void EnforceExpectedDataTransferLength(SCSIResponsePDU response, uint expectedDataTransferLength)
  173. {
  174. if (response.Data.Length > expectedDataTransferLength)
  175. {
  176. response.ResidualOverflow = true;
  177. response.ResidualCount = (uint)(response.Data.Length - expectedDataTransferLength);
  178. response.Data = ByteReader.ReadBytes(response.Data, 0, (int)expectedDataTransferLength);
  179. }
  180. else if (response.Data.Length < expectedDataTransferLength)
  181. {
  182. response.ResidualUnderflow = true;
  183. response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
  184. }
  185. }
  186. public static void EnforceExpectedDataTransferLength(SCSIDataInPDU response, uint expectedDataTransferLength)
  187. {
  188. if (response.Data.Length > expectedDataTransferLength)
  189. {
  190. response.ResidualOverflow = true;
  191. response.ResidualCount = (uint)(response.Data.Length - expectedDataTransferLength);
  192. response.Data = ByteReader.ReadBytes(response.Data, 0, (int)expectedDataTransferLength);
  193. }
  194. else if (response.Data.Length < expectedDataTransferLength)
  195. {
  196. response.ResidualUnderflow = true;
  197. response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
  198. }
  199. }
  200. }
  201. }