SshDataStream.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  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. var bytes = encoding.GetBytes(s);
  120. WriteBinary(bytes, 0, bytes.Length);
  121. }
  122. /// <summary>
  123. /// Reads a byte array from the SSH data stream.
  124. /// </summary>
  125. /// <returns>
  126. /// The byte array read from the SSH data stream.
  127. /// </returns>
  128. public byte[] ReadBinary()
  129. {
  130. var length = ReadUInt32();
  131. if (length > int.MaxValue)
  132. {
  133. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Data longer than {0} is not supported.", int.MaxValue));
  134. }
  135. return ReadBytes((int)length);
  136. }
  137. /// <summary>
  138. /// Writes a buffer preceded by its length into the SSH data stream.
  139. /// </summary>
  140. /// <param name="buffer">The data to write.</param>
  141. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
  142. public void WriteBinary(byte[] buffer)
  143. {
  144. if (buffer is null)
  145. {
  146. throw new ArgumentNullException(nameof(buffer));
  147. }
  148. WriteBinary(buffer, 0, buffer.Length);
  149. }
  150. /// <summary>
  151. /// Writes a buffer preceded by its length into the SSH data stream.
  152. /// </summary>
  153. /// <param name="buffer">An array of bytes. This method write <paramref name="count"/> bytes from buffer to the current SSH data stream.</param>
  154. /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin writing bytes to the SSH data stream.</param>
  155. /// <param name="count">The number of bytes to be written to the current SSH data stream.</param>
  156. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
  157. /// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is greater than the buffer length.</exception>
  158. /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negative.</exception>
  159. public void WriteBinary(byte[] buffer, int offset, int count)
  160. {
  161. Write((uint) count);
  162. Write(buffer, offset, count);
  163. }
  164. /// <summary>
  165. /// Reads a <see cref="BigInteger"/> from the SSH datastream.
  166. /// </summary>
  167. /// <returns>
  168. /// The <see cref="BigInteger"/> read from the SSH data stream.
  169. /// </returns>
  170. public BigInteger ReadBigInt()
  171. {
  172. var length = ReadUInt32();
  173. var data = ReadBytes((int) length);
  174. return new BigInteger(data.Reverse());
  175. }
  176. /// <summary>
  177. /// Reads the next <see cref="ushort"/> data type from the SSH data stream.
  178. /// </summary>
  179. /// <returns>
  180. /// The <see cref="ushort"/> read from the SSH data stream.
  181. /// </returns>
  182. public ushort ReadUInt16()
  183. {
  184. #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
  185. Span<byte> bytes = stackalloc byte[2];
  186. ReadBytes(bytes);
  187. return System.Buffers.Binary.BinaryPrimitives.ReadUInt16BigEndian(bytes);
  188. #else
  189. var data = ReadBytes(2);
  190. return Pack.BigEndianToUInt16(data);
  191. #endif
  192. }
  193. /// <summary>
  194. /// Reads the next <see cref="uint"/> data type from the SSH data stream.
  195. /// </summary>
  196. /// <returns>
  197. /// The <see cref="uint"/> read from the SSH data stream.
  198. /// </returns>
  199. public uint ReadUInt32()
  200. {
  201. #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
  202. Span<byte> span = stackalloc byte[4];
  203. ReadBytes(span);
  204. return System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(span);
  205. #else
  206. var data = ReadBytes(4);
  207. return Pack.BigEndianToUInt32(data);
  208. #endif // NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
  209. }
  210. /// <summary>
  211. /// Reads the next <see cref="ulong"/> data type from the SSH data stream.
  212. /// </summary>
  213. /// <returns>
  214. /// The <see cref="ulong"/> read from the SSH data stream.
  215. /// </returns>
  216. public ulong ReadUInt64()
  217. {
  218. #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
  219. Span<byte> span = stackalloc byte[8];
  220. ReadBytes(span);
  221. return System.Buffers.Binary.BinaryPrimitives.ReadUInt64BigEndian(span);
  222. #else
  223. var data = ReadBytes(8);
  224. return Pack.BigEndianToUInt64(data);
  225. #endif // NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER
  226. }
  227. /// <summary>
  228. /// Reads the next <see cref="string"/> data type from the SSH data stream.
  229. /// </summary>
  230. /// <param name="encoding">The character encoding to use.</param>
  231. /// <returns>
  232. /// The <see cref="string"/> read from the SSH data stream.
  233. /// </returns>
  234. public string ReadString(Encoding encoding)
  235. {
  236. var length = ReadUInt32();
  237. if (length > int.MaxValue)
  238. {
  239. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue));
  240. }
  241. var bytes = ReadBytes((int) length);
  242. return encoding.GetString(bytes, 0, bytes.Length);
  243. }
  244. /// <summary>
  245. /// Writes the stream contents to a byte array, regardless of the <see cref="MemoryStream.Position"/>.
  246. /// </summary>
  247. /// <returns>
  248. /// This method returns the contents of the <see cref="SshDataStream"/> as a byte array.
  249. /// </returns>
  250. /// <remarks>
  251. /// If the current instance was constructed on a provided byte array, a copy of the section of the array
  252. /// to which this instance has access is returned.
  253. /// </remarks>
  254. public override byte[] ToArray()
  255. {
  256. if (Capacity == Length)
  257. {
  258. return GetBuffer();
  259. }
  260. return base.ToArray();
  261. }
  262. /// <summary>
  263. /// Reads next specified number of bytes data type from internal buffer.
  264. /// </summary>
  265. /// <param name="length">Number of bytes to read.</param>
  266. /// <returns>
  267. /// An array of bytes that was read from the internal buffer.
  268. /// </returns>
  269. /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is greater than the internal buffer size.</exception>
  270. internal byte[] ReadBytes(int length)
  271. {
  272. var data = new byte[length];
  273. var bytesRead = Read(data, 0, length);
  274. if (bytesRead < length)
  275. {
  276. 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));
  277. }
  278. return data;
  279. }
  280. #if NETSTANDARD2_1 || NET6_0_OR_GREATER
  281. /// <summary>
  282. /// Reads data into the specified <paramref name="buffer" />.
  283. /// </summary>
  284. /// <param name="buffer">The buffer to read into.</param>
  285. /// <exception cref="ArgumentOutOfRangeException"><paramref name="buffer"/> is larger than the total of bytes available.</exception>
  286. private void ReadBytes(Span<byte> buffer)
  287. {
  288. var bytesRead = Read(buffer);
  289. if (bytesRead < buffer.Length)
  290. {
  291. 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));
  292. }
  293. }
  294. #endif // NETSTANDARD2_1 || NET6_0_OR_GREATER
  295. }
  296. }