TargetResponseHelper.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. /* Copyright (C) 2012-2018 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 SCSI;
  14. using Utilities;
  15. namespace ISCSI.Server
  16. {
  17. internal class TargetResponseHelper
  18. {
  19. internal static List<ReadyToTransferPDU> GetReadyToTransferPDUs(SCSICommandPDU command, ConnectionParameters connection, out List<SCSICommandPDU> commandsToExecute)
  20. {
  21. // We return either SCSIResponsePDU or List<SCSIDataInPDU>
  22. List<ReadyToTransferPDU> responseList = new List<ReadyToTransferPDU>();
  23. commandsToExecute = new List<SCSICommandPDU>();
  24. ISCSISession session = connection.Session;
  25. if (command.Write && command.DataSegmentLength < command.ExpectedDataTransferLength)
  26. {
  27. uint transferTag = session.GetNextTransferTag();
  28. // Create buffer for next segments (we only execute the command after receiving all of its data)
  29. Array.Resize<byte>(ref command.Data, (int)command.ExpectedDataTransferLength);
  30. // Send R2Ts:
  31. uint bytesLeft = command.ExpectedDataTransferLength - command.DataSegmentLength;
  32. uint nextOffset = command.DataSegmentLength;
  33. if (!session.InitialR2T)
  34. {
  35. uint firstDataPDULength = Math.Min((uint)session.FirstBurstLength, command.ExpectedDataTransferLength) - command.DataSegmentLength;
  36. bytesLeft -= firstDataPDULength;
  37. nextOffset += firstDataPDULength;
  38. }
  39. int totalR2Ts = (int)Math.Ceiling((double)bytesLeft / connection.TargetMaxRecvDataSegmentLength);
  40. int outgoingR2Ts = Math.Min(session.MaxOutstandingR2T, totalR2Ts);
  41. for (uint index = 0; index < outgoingR2Ts; index++)
  42. {
  43. ReadyToTransferPDU response = new ReadyToTransferPDU();
  44. response.InitiatorTaskTag = command.InitiatorTaskTag;
  45. response.R2TSN = index; // R2Ts are sequenced per command and must start with 0 for each new command;
  46. response.TargetTransferTag = transferTag;
  47. response.BufferOffset = nextOffset;
  48. response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);
  49. responseList.Add(response);
  50. nextOffset += (uint)connection.TargetMaxRecvDataSegmentLength;
  51. }
  52. connection.AddTransfer(command.InitiatorTaskTag, transferTag, command, (uint)outgoingR2Ts, nextOffset, (uint)totalR2Ts);
  53. session.CommandsInTransfer.Add(command.CmdSN);
  54. return responseList;
  55. }
  56. if (session.IsPrecedingCommandPending(command.CmdSN))
  57. {
  58. session.DelayedCommands.Add(command);
  59. }
  60. else
  61. {
  62. commandsToExecute.Add(command);
  63. }
  64. return responseList;
  65. }
  66. internal static List<ReadyToTransferPDU> GetReadyToTransferPDUs(SCSIDataOutPDU request, ConnectionParameters connection, out List<SCSICommandPDU> commandsToExecute)
  67. {
  68. List<ReadyToTransferPDU> responseList = new List<ReadyToTransferPDU>();
  69. commandsToExecute = new List<SCSICommandPDU>();
  70. ISCSISession session = connection.Session;
  71. TransferEntry transfer = null;
  72. if (request.TargetTransferTag != 0xFFFFFFFF) // 0xFFFFFFFF means Target Transfer Tag is not supplied
  73. {
  74. transfer = connection.GetTransferEntry(request.TargetTransferTag);
  75. }
  76. else if (!session.InitialR2T)
  77. {
  78. transfer = connection.GetTransferEntryUsingTaskTag(request.InitiatorTaskTag);
  79. }
  80. if (transfer == null)
  81. {
  82. throw new InvalidTargetTransferTagException(request.TargetTransferTag);
  83. }
  84. uint offset = request.BufferOffset;
  85. uint totalLength = (uint)transfer.Command.ExpectedDataTransferLength;
  86. // Store segment (we only execute the command after receiving all of its data)
  87. Array.Copy(request.Data, 0, transfer.Command.Data, offset, request.DataSegmentLength);
  88. if (offset + request.DataSegmentLength == totalLength)
  89. {
  90. // Last Data-out PDU
  91. connection.RemoveTransfer(request.InitiatorTaskTag, request.TargetTransferTag);
  92. session.CommandsInTransfer.Remove(transfer.Command.CmdSN);
  93. if (session.IsPrecedingCommandPending(transfer.Command.CmdSN))
  94. {
  95. session.DelayedCommands.Add(transfer.Command);
  96. }
  97. else
  98. {
  99. commandsToExecute.Add(transfer.Command);
  100. // Check if delayed commands are ready to be executed
  101. List<SCSICommandPDU> pendingCommands = session.GetDelayedCommandsReadyForExecution();
  102. foreach (SCSICommandPDU pendingCommand in pendingCommands)
  103. {
  104. commandsToExecute.Add(pendingCommand);
  105. }
  106. }
  107. return responseList;
  108. }
  109. else
  110. {
  111. // RFC 3720: An R2T MAY be answered with one or more SCSI Data-Out PDUs with a matching Target Transfer Tag.
  112. // If an R2T is answered with a single Data-Out PDU, the Buffer Offset in the Data PDU MUST be the same as the one specified
  113. // by the R2T, and the data length of the Data PDU MUST be the same as the Desired Data Transfer Length specified in the R2T.
  114. // If the R2T is answered with a sequence of Data PDUs, the Buffer Offset and Length MUST be within
  115. // the range of those specified by R2T, and the last PDU MUST have the F bit set to 1.
  116. // An R2T is considered outstanding until the last data PDU is transferred.
  117. if (request.Final)
  118. {
  119. // We already sent as many R2T as we could, we will only send R2T if any remained.
  120. if (transfer.NextR2TSN < transfer.TotalR2Ts)
  121. {
  122. // Send R2T
  123. ReadyToTransferPDU response = new ReadyToTransferPDU();
  124. response.InitiatorTaskTag = request.InitiatorTaskTag;
  125. response.TargetTransferTag = request.TargetTransferTag;
  126. response.R2TSN = transfer.NextR2TSN;
  127. response.BufferOffset = transfer.NextOffset; // where we left off
  128. response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, totalLength - response.BufferOffset);
  129. responseList.Add(response);
  130. transfer.NextR2TSN++;
  131. transfer.NextOffset += (uint)connection.TargetMaxRecvDataSegmentLength;
  132. }
  133. }
  134. return responseList;
  135. }
  136. }
  137. internal static List<ISCSIPDU> PrepareSCSICommandResponse(SCSICommandPDU command, SCSIStatusCodeName status, byte[] scsiResponse, ConnectionParameters connection)
  138. {
  139. List<ISCSIPDU> responseList = new List<ISCSIPDU>();
  140. if (!command.Read || status != SCSIStatusCodeName.Good)
  141. {
  142. // RFC 3720: if the command is completed with an error, then the response and sense data MUST be sent in a SCSI Response PDU
  143. SCSIResponsePDU response = new SCSIResponsePDU();
  144. response.InitiatorTaskTag = command.InitiatorTaskTag;
  145. response.Status = status;
  146. response.Data = scsiResponse;
  147. if (command.Read)
  148. {
  149. EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
  150. }
  151. responseList.Add(response);
  152. }
  153. else if (scsiResponse.Length <= connection.InitiatorMaxRecvDataSegmentLength)
  154. {
  155. SCSIDataInPDU response = new SCSIDataInPDU();
  156. response.InitiatorTaskTag = command.InitiatorTaskTag;
  157. response.Status = status;
  158. response.StatusPresent = true;
  159. response.Final = true;
  160. response.Data = scsiResponse;
  161. EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
  162. responseList.Add(response);
  163. }
  164. else // we have to split the response to multiple Data-In PDUs
  165. {
  166. int bytesLeftToSend = scsiResponse.Length;
  167. uint dataSN = 0;
  168. while (bytesLeftToSend > 0)
  169. {
  170. int dataSegmentLength = Math.Min(connection.InitiatorMaxRecvDataSegmentLength, bytesLeftToSend);
  171. int dataOffset = scsiResponse.Length - bytesLeftToSend;
  172. SCSIDataInPDU response = new SCSIDataInPDU();
  173. response.InitiatorTaskTag = command.InitiatorTaskTag;
  174. if (bytesLeftToSend == dataSegmentLength)
  175. {
  176. // last Data-In PDU
  177. response.Status = status;
  178. response.StatusPresent = true;
  179. response.Final = true;
  180. }
  181. response.BufferOffset = (uint)dataOffset;
  182. response.DataSN = dataSN;
  183. dataSN++;
  184. response.Data = new byte[dataSegmentLength];
  185. Array.Copy(scsiResponse, dataOffset, response.Data, 0, dataSegmentLength);
  186. responseList.Add(response);
  187. bytesLeftToSend -= dataSegmentLength;
  188. }
  189. }
  190. return responseList;
  191. }
  192. public static void EnforceExpectedDataTransferLength(SCSIResponsePDU response, uint expectedDataTransferLength)
  193. {
  194. if (response.Data.Length > expectedDataTransferLength)
  195. {
  196. response.ResidualOverflow = true;
  197. response.ResidualCount = (uint)(response.Data.Length - expectedDataTransferLength);
  198. response.Data = ByteReader.ReadBytes(response.Data, 0, (int)expectedDataTransferLength);
  199. }
  200. else if (response.Data.Length < expectedDataTransferLength)
  201. {
  202. response.ResidualUnderflow = true;
  203. response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
  204. }
  205. }
  206. public static void EnforceExpectedDataTransferLength(SCSIDataInPDU response, uint expectedDataTransferLength)
  207. {
  208. if (response.Data.Length > expectedDataTransferLength)
  209. {
  210. response.ResidualOverflow = true;
  211. response.ResidualCount = (uint)(response.Data.Length - expectedDataTransferLength);
  212. response.Data = ByteReader.ReadBytes(response.Data, 0, (int)expectedDataTransferLength);
  213. }
  214. else if (response.Data.Length < expectedDataTransferLength)
  215. {
  216. response.ResidualUnderflow = true;
  217. response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
  218. }
  219. }
  220. }
  221. }