SshDataStream.cs 13 KB

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