2
0

SshDataStream.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. using System;
  2. using System.Globalization;
  3. using System.IO;
  4. using System.Numerics;
  5. using System.Text;
  6. namespace Renci.SshNet.Common
  7. {
  8. /// <summary>
  9. /// Specialized <see cref="MemoryStream"/> for reading and writing data SSH data.
  10. /// </summary>
  11. public class SshDataStream : MemoryStream
  12. {
  13. /// <summary>
  14. /// Initializes a new instance of the <see cref="SshDataStream"/> class with an expandable capacity initialized
  15. /// as specified.
  16. /// </summary>
  17. /// <param name="capacity">The initial size of the internal array in bytes.</param>
  18. public SshDataStream(int capacity)
  19. : base(capacity)
  20. {
  21. }
  22. /// <summary>
  23. /// Initializes a new instance of the <see cref="SshDataStream"/> class for the specified byte array.
  24. /// </summary>
  25. /// <param name="buffer">The array of unsigned bytes from which to create the current stream.</param>
  26. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
  27. public SshDataStream(byte[] buffer)
  28. : base(buffer)
  29. {
  30. }
  31. /// <summary>
  32. /// Initializes a new instance of the <see cref="SshDataStream"/> class for the specified byte array.
  33. /// </summary>
  34. /// <param name="buffer">The array of unsigned bytes from which to create the current stream.</param>
  35. /// <param name="offset">The zero-based offset in <paramref name="buffer"/> at which to begin reading SSH data.</param>
  36. /// <param name="count">The number of bytes to load.</param>
  37. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
  38. public SshDataStream(byte[] buffer, int offset, int count)
  39. : base(buffer, offset, count)
  40. {
  41. }
  42. /// <summary>
  43. /// Gets a value indicating whether all data from the SSH data stream has been read.
  44. /// </summary>
  45. /// <value>
  46. /// <see langword="true"/> if this instance is end of data; otherwise, <see langword="false"/>.
  47. /// </value>
  48. public bool IsEndOfData
  49. {
  50. get
  51. {
  52. return Position >= Length;
  53. }
  54. }
  55. #if NETFRAMEWORK || NETSTANDARD2_0
  56. private int Read(Span<byte> buffer)
  57. {
  58. var sharedBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(buffer.Length);
  59. var numRead = Read(sharedBuffer, 0, buffer.Length);
  60. sharedBuffer.AsSpan(0, numRead).CopyTo(buffer);
  61. System.Buffers.ArrayPool<byte>.Shared.Return(sharedBuffer);
  62. return numRead;
  63. }
  64. private void Write(ReadOnlySpan<byte> buffer)
  65. {
  66. var sharedBuffer = System.Buffers.ArrayPool<byte>.Shared.Rent(buffer.Length);
  67. buffer.CopyTo(sharedBuffer);
  68. Write(sharedBuffer, 0, buffer.Length);
  69. System.Buffers.ArrayPool<byte>.Shared.Return(sharedBuffer);
  70. }
  71. #endif
  72. /// <summary>
  73. /// Writes an <see cref="uint"/> to the SSH data stream.
  74. /// </summary>
  75. /// <param name="value"><see cref="uint"/> data to write.</param>
  76. public void Write(uint value)
  77. {
  78. Span<byte> bytes = stackalloc byte[4];
  79. System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(bytes, value);
  80. Write(bytes);
  81. }
  82. /// <summary>
  83. /// Writes an <see cref="ulong"/> to the SSH data stream.
  84. /// </summary>
  85. /// <param name="value"><see cref="ulong"/> data to write.</param>
  86. public void Write(ulong value)
  87. {
  88. Span<byte> bytes = stackalloc byte[8];
  89. System.Buffers.Binary.BinaryPrimitives.WriteUInt64BigEndian(bytes, value);
  90. Write(bytes);
  91. }
  92. /// <summary>
  93. /// Writes a <see cref="BigInteger"/> into the SSH data stream.
  94. /// </summary>
  95. /// <param name="data">The <see cref="BigInteger" /> to write.</param>
  96. public void Write(BigInteger data)
  97. {
  98. var bytes = data.ToByteArray(isBigEndian: true);
  99. WriteBinary(bytes, 0, bytes.Length);
  100. }
  101. /// <summary>
  102. /// Writes bytes array data into the SSH data stream.
  103. /// </summary>
  104. /// <param name="data">Byte array data to write.</param>
  105. /// <exception cref="ArgumentNullException"><paramref name="data"/> is <see langword="null"/>.</exception>
  106. public void Write(byte[] data)
  107. {
  108. ThrowHelper.ThrowIfNull(data);
  109. Write(data, 0, data.Length);
  110. }
  111. /// <summary>
  112. /// Writes string data to the SSH data stream using the specified encoding.
  113. /// </summary>
  114. /// <param name="s">The string data to write.</param>
  115. /// <param name="encoding">The character encoding to use.</param>
  116. /// <exception cref="ArgumentNullException"><paramref name="s"/> is <see langword="null"/>.</exception>
  117. /// <exception cref="ArgumentNullException"><paramref name="encoding"/> is <see langword="null"/>.</exception>
  118. public void Write(string s, Encoding encoding)
  119. {
  120. ThrowHelper.ThrowIfNull(encoding);
  121. #if NETSTANDARD2_1 || NET
  122. ReadOnlySpan<char> value = s;
  123. var count = encoding.GetByteCount(value);
  124. var bytes = count <= 256 ? stackalloc byte[count] : new byte[count];
  125. encoding.GetBytes(value, bytes);
  126. Write((uint)count);
  127. Write(bytes);
  128. #else
  129. var bytes = encoding.GetBytes(s);
  130. WriteBinary(bytes, 0, bytes.Length);
  131. #endif
  132. }
  133. /// <summary>
  134. /// Reads a byte array from the SSH data stream.
  135. /// </summary>
  136. /// <returns>
  137. /// The byte array read from the SSH data stream.
  138. /// </returns>
  139. public byte[] ReadBinary()
  140. {
  141. var length = ReadUInt32();
  142. if (length > int.MaxValue)
  143. {
  144. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Data longer than {0} is not supported.", int.MaxValue));
  145. }
  146. return ReadBytes((int)length);
  147. }
  148. /// <summary>
  149. /// Writes a buffer preceded by its length into the SSH data stream.
  150. /// </summary>
  151. /// <param name="buffer">The data to write.</param>
  152. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
  153. public void WriteBinary(byte[] buffer)
  154. {
  155. ThrowHelper.ThrowIfNull(buffer);
  156. WriteBinary(buffer, 0, buffer.Length);
  157. }
  158. /// <summary>
  159. /// Writes a buffer preceded by its length into the SSH data stream.
  160. /// </summary>
  161. /// <param name="buffer">An array of bytes. This method write <paramref name="count"/> bytes from buffer to the current SSH data stream.</param>
  162. /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin writing bytes to the SSH data stream.</param>
  163. /// <param name="count">The number of bytes to be written to the current SSH data stream.</param>
  164. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
  165. /// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is greater than the buffer length.</exception>
  166. /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negative.</exception>
  167. public void WriteBinary(byte[] buffer, int offset, int count)
  168. {
  169. Write((uint)count);
  170. Write(buffer, offset, count);
  171. }
  172. /// <summary>
  173. /// Reads a <see cref="BigInteger"/> from the SSH datastream.
  174. /// </summary>
  175. /// <returns>
  176. /// The <see cref="BigInteger"/> read from the SSH data stream.
  177. /// </returns>
  178. public BigInteger ReadBigInt()
  179. {
  180. var data = ReadBinary();
  181. #if NETSTANDARD2_1 || NET
  182. return new BigInteger(data, isBigEndian: true);
  183. #else
  184. return new BigInteger(data.Reverse());
  185. #endif
  186. }
  187. /// <summary>
  188. /// Reads the next <see cref="ushort"/> data type from the SSH data stream.
  189. /// </summary>
  190. /// <returns>
  191. /// The <see cref="ushort"/> read from the SSH data stream.
  192. /// </returns>
  193. public ushort ReadUInt16()
  194. {
  195. Span<byte> bytes = stackalloc byte[2];
  196. ReadBytes(bytes);
  197. return System.Buffers.Binary.BinaryPrimitives.ReadUInt16BigEndian(bytes);
  198. }
  199. /// <summary>
  200. /// Reads the next <see cref="uint"/> data type from the SSH data stream.
  201. /// </summary>
  202. /// <returns>
  203. /// The <see cref="uint"/> read from the SSH data stream.
  204. /// </returns>
  205. public uint ReadUInt32()
  206. {
  207. Span<byte> span = stackalloc byte[4];
  208. ReadBytes(span);
  209. return System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(span);
  210. }
  211. /// <summary>
  212. /// Reads the next <see cref="ulong"/> data type from the SSH data stream.
  213. /// </summary>
  214. /// <returns>
  215. /// The <see cref="ulong"/> read from the SSH data stream.
  216. /// </returns>
  217. public ulong ReadUInt64()
  218. {
  219. Span<byte> span = stackalloc byte[8];
  220. ReadBytes(span);
  221. return System.Buffers.Binary.BinaryPrimitives.ReadUInt64BigEndian(span);
  222. }
  223. /// <summary>
  224. /// Reads the next <see cref="string"/> data type from the SSH data stream.
  225. /// </summary>
  226. /// <param name="encoding">The character encoding to use. Defaults to <see cref="Encoding.UTF8"/>.</param>
  227. /// <returns>
  228. /// The <see cref="string"/> read from the SSH data stream.
  229. /// </returns>
  230. public string ReadString(Encoding encoding = null)
  231. {
  232. encoding ??= Encoding.UTF8;
  233. var length = ReadUInt32();
  234. if (length > int.MaxValue)
  235. {
  236. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue));
  237. }
  238. var bytes = ReadBytes((int)length);
  239. return encoding.GetString(bytes, 0, bytes.Length);
  240. }
  241. /// <summary>
  242. /// Writes the stream contents to a byte array, regardless of the <see cref="MemoryStream.Position"/>.
  243. /// </summary>
  244. /// <returns>
  245. /// This method returns the contents of the <see cref="SshDataStream"/> as a byte array.
  246. /// </returns>
  247. /// <remarks>
  248. /// If the current instance was constructed on a provided byte array, a copy of the section of the array
  249. /// to which this instance has access is returned.
  250. /// </remarks>
  251. public override byte[] ToArray()
  252. {
  253. if (Capacity == Length)
  254. {
  255. return GetBuffer();
  256. }
  257. return base.ToArray();
  258. }
  259. /// <summary>
  260. /// Reads next specified number of bytes data type from internal buffer.
  261. /// </summary>
  262. /// <param name="length">Number of bytes to read.</param>
  263. /// <returns>
  264. /// An array of bytes that was read from the internal buffer.
  265. /// </returns>
  266. /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is greater than the internal buffer size.</exception>
  267. internal byte[] ReadBytes(int length)
  268. {
  269. var data = new byte[length];
  270. var bytesRead = Read(data, 0, length);
  271. if (bytesRead < length)
  272. {
  273. throw new ArgumentOutOfRangeException(nameof(length), string.Format(CultureInfo.InvariantCulture, "The requested length ({0}) is greater than the actual number of bytes read ({1}).", length, bytesRead));
  274. }
  275. return data;
  276. }
  277. /// <summary>
  278. /// Reads data into the specified <paramref name="buffer" />.
  279. /// </summary>
  280. /// <param name="buffer">The buffer to read into.</param>
  281. /// <exception cref="ArgumentOutOfRangeException"><paramref name="buffer"/> is larger than the total of bytes available.</exception>
  282. private void ReadBytes(Span<byte> buffer)
  283. {
  284. var bytesRead = Read(buffer);
  285. if (bytesRead < buffer.Length)
  286. {
  287. throw new ArgumentOutOfRangeException(nameof(buffer), string.Format(CultureInfo.InvariantCulture, "The requested length ({0}) is greater than the actual number of bytes read ({1}).", buffer.Length, bytesRead));
  288. }
  289. }
  290. }
  291. }