TargetResponseHelper.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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 SCSI;
  14. using Utilities;
  15. namespace ISCSI.Server
  16. {
  17. public class TargetResponseHelper
  18. {
  19. internal static List<ReadyToTransferPDU> GetReadyToTransferPDUs(SCSICommandPDU command, SCSITarget target, SessionParameters session, 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. string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
  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 R2T
  31. ReadyToTransferPDU response = new ReadyToTransferPDU();
  32. response.InitiatorTaskTag = command.InitiatorTaskTag;
  33. response.R2TSN = 0; // R2Ts are sequenced per command and must start with 0 for each new command;
  34. response.TargetTransferTag = transferTag;
  35. response.BufferOffset = command.DataSegmentLength;
  36. response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, command.ExpectedDataTransferLength - response.BufferOffset);
  37. connection.AddTransfer(transferTag, command, 1);
  38. session.CommandsInTransfer.Add(command.CmdSN);
  39. responseList.Add(response);
  40. return responseList;
  41. }
  42. if (session.IsPrecedingCommandPending(command.CmdSN))
  43. {
  44. session.DelayedCommands.Add(command);
  45. }
  46. else
  47. {
  48. commandsToExecute.Add(command);
  49. }
  50. return responseList;
  51. }
  52. internal static List<ReadyToTransferPDU> GetReadyToTransferPDUs(SCSIDataOutPDU request, SCSITarget target, SessionParameters session, ConnectionParameters connection, out List<SCSICommandPDU> commandsToExecute)
  53. {
  54. List<ReadyToTransferPDU> responseList = new List<ReadyToTransferPDU>();
  55. commandsToExecute = new List<SCSICommandPDU>();
  56. string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
  57. TransferEntry transfer = connection.GetTransferEntry(request.TargetTransferTag);
  58. if (transfer == null)
  59. {
  60. throw new InvalidTargetTransferTagException(request.TargetTransferTag);
  61. }
  62. uint offset = request.BufferOffset;
  63. uint totalLength = (uint)transfer.Command.ExpectedDataTransferLength;
  64. // Store segment (we only execute the command after receiving all of its data)
  65. Array.Copy(request.Data, 0, transfer.Command.Data, offset, request.DataSegmentLength);
  66. ISCSIServer.Log(String.Format("[{0}][GetSCSIDataOutResponsePDU] Buffer offset: {1}, Total length: {2}", connectionIdentifier, offset, totalLength));
  67. if (offset + request.DataSegmentLength == totalLength)
  68. {
  69. // Last Data-out PDU
  70. if (session.IsPrecedingCommandPending(transfer.Command.CmdSN))
  71. {
  72. session.DelayedCommands.Add(transfer.Command);
  73. }
  74. else
  75. {
  76. commandsToExecute.Add(transfer.Command);
  77. connection.RemoveTransfer(request.TargetTransferTag);
  78. session.CommandsInTransfer.Remove(transfer.Command.CmdSN);
  79. // Check if delayed commands are ready to be executed
  80. List<SCSICommandPDU> pendingCommands = session.GetDelayedCommandsReadyForExecution();
  81. foreach (SCSICommandPDU pendingCommand in pendingCommands)
  82. {
  83. commandsToExecute.Add(pendingCommand);
  84. }
  85. }
  86. return responseList;
  87. }
  88. else
  89. {
  90. // Send R2T
  91. ReadyToTransferPDU response = new ReadyToTransferPDU();
  92. response.InitiatorTaskTag = request.InitiatorTaskTag;
  93. response.TargetTransferTag = request.TargetTransferTag;
  94. response.R2TSN = transfer.NextR2NSN;
  95. response.BufferOffset = offset + request.DataSegmentLength; // where we left off
  96. response.DesiredDataTransferLength = Math.Min((uint)connection.TargetMaxRecvDataSegmentLength, totalLength - response.BufferOffset);
  97. transfer.NextR2NSN++;
  98. responseList.Add(response);
  99. return responseList;
  100. }
  101. }
  102. internal static List<ISCSIPDU> GetSCSICommandResponse(SCSICommandPDU command, SCSITarget target, SessionParameters session, ConnectionParameters connection)
  103. {
  104. string connectionIdentifier = ConnectionState.GetConnectionIdentifier(session, connection);
  105. ISCSIServer.Log("[{0}] Executing Command: CmdSN: {1}", connectionIdentifier, command.CmdSN);
  106. byte[] scsiResponse;
  107. SCSIStatusCodeName status = target.ExecuteCommand(command.CommandDescriptorBlock, command.LUN, command.Data, out scsiResponse);
  108. return PrepareSCSICommandResponse(command, status, scsiResponse, connection);
  109. }
  110. internal static List<ISCSIPDU> PrepareSCSICommandResponse(SCSICommandPDU command, SCSIStatusCodeName status, byte[] scsiResponse, ConnectionParameters connection)
  111. {
  112. List<ISCSIPDU> responseList = new List<ISCSIPDU>();
  113. if (!command.Read || status != SCSIStatusCodeName.Good)
  114. {
  115. // RFC 3720: if the command is completed with an error, then the response and sense data MUST be sent in a SCSI Response PDU
  116. SCSIResponsePDU response = new SCSIResponsePDU();
  117. response.InitiatorTaskTag = command.InitiatorTaskTag;
  118. response.Status = status;
  119. response.Data = scsiResponse;
  120. if (command.Read)
  121. {
  122. EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
  123. }
  124. responseList.Add(response);
  125. }
  126. else if (scsiResponse.Length <= connection.InitiatorMaxRecvDataSegmentLength)
  127. {
  128. SCSIDataInPDU response = new SCSIDataInPDU();
  129. response.InitiatorTaskTag = command.InitiatorTaskTag;
  130. response.Status = status;
  131. response.StatusPresent = true;
  132. response.Final = true;
  133. response.Data = scsiResponse;
  134. EnforceExpectedDataTransferLength(response, command.ExpectedDataTransferLength);
  135. responseList.Add(response);
  136. }
  137. else // we have to split the response to multiple Data-In PDUs
  138. {
  139. int bytesLeftToSend = scsiResponse.Length;
  140. uint dataSN = 0;
  141. while (bytesLeftToSend > 0)
  142. {
  143. int dataSegmentLength = Math.Min(connection.InitiatorMaxRecvDataSegmentLength, bytesLeftToSend);
  144. int dataOffset = scsiResponse.Length - bytesLeftToSend;
  145. SCSIDataInPDU response = new SCSIDataInPDU();
  146. response.InitiatorTaskTag = command.InitiatorTaskTag;
  147. if (bytesLeftToSend == dataSegmentLength)
  148. {
  149. // last Data-In PDU
  150. response.Status = status;
  151. response.StatusPresent = true;
  152. response.Final = true;
  153. }
  154. response.BufferOffset = (uint)dataOffset;
  155. response.DataSN = dataSN;
  156. dataSN++;
  157. response.Data = new byte[dataSegmentLength];
  158. Array.Copy(scsiResponse, dataOffset, response.Data, 0, dataSegmentLength);
  159. responseList.Add(response);
  160. bytesLeftToSend -= dataSegmentLength;
  161. }
  162. }
  163. return responseList;
  164. }
  165. public static void EnforceExpectedDataTransferLength(SCSIResponsePDU response, uint expectedDataTransferLength)
  166. {
  167. if (response.Data.Length > expectedDataTransferLength)
  168. {
  169. response.ResidualOverflow = true;
  170. response.ResidualCount = (uint)(response.Data.Length - expectedDataTransferLength);
  171. response.Data = ByteReader.ReadBytes(response.Data, 0, (int)expectedDataTransferLength);
  172. }
  173. else if (response.Data.Length < expectedDataTransferLength)
  174. {
  175. response.ResidualUnderflow = true;
  176. response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
  177. }
  178. }
  179. public static void EnforceExpectedDataTransferLength(SCSIDataInPDU response, uint expectedDataTransferLength)
  180. {
  181. if (response.Data.Length > expectedDataTransferLength)
  182. {
  183. response.ResidualOverflow = true;
  184. response.ResidualCount = (uint)(response.Data.Length - expectedDataTransferLength);
  185. response.Data = ByteReader.ReadBytes(response.Data, 0, (int)expectedDataTransferLength);
  186. }
  187. else if (response.Data.Length < expectedDataTransferLength)
  188. {
  189. response.ResidualUnderflow = true;
  190. response.ResidualCount = (uint)(expectedDataTransferLength - response.Data.Length);
  191. }
  192. }
  193. }
  194. }