using System; using System.Globalization; using System.IO; using System.Text; namespace Renci.SshNet.Common { /// /// Specialized for reading and writing data SSH data. /// public class SshDataStream : MemoryStream { /// /// Initializes a new instance of the class with an expandable capacity initialized /// as specified. /// /// The initial size of the internal array in bytes. public SshDataStream(int capacity) : base(capacity) { } /// /// Initializes a new instance of the class for the specified byte array. /// /// The array of unsigned bytes from which to create the current stream. /// is . public SshDataStream(byte[] buffer) : base(buffer) { } /// /// Initializes a new instance of the class for the specified byte array. /// /// The array of unsigned bytes from which to create the current stream. /// The zero-based offset in at which to begin reading SSH data. /// The number of bytes to load. /// is . public SshDataStream(byte[] buffer, int offset, int count) : base(buffer, offset, count) { } /// /// Gets a value indicating whether all data from the SSH data stream has been read. /// /// /// if this instance is end of data; otherwise, . /// public bool IsEndOfData { get { return Position >= Length; } } /// /// Writes an to the SSH data stream. /// /// data to write. public void Write(uint value) { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER Span bytes = stackalloc byte[4]; System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(bytes, value); Write(bytes); #else var bytes = Pack.UInt32ToBigEndian(value); Write(bytes, 0, bytes.Length); #endif } /// /// Writes an to the SSH data stream. /// /// data to write. public void Write(ulong value) { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER Span bytes = stackalloc byte[8]; System.Buffers.Binary.BinaryPrimitives.WriteUInt64BigEndian(bytes, value); Write(bytes); #else var bytes = Pack.UInt64ToBigEndian(value); Write(bytes, 0, bytes.Length); #endif } /// /// Writes a into the SSH data stream. /// /// The to write. public void Write(BigInteger data) { var bytes = data.ToByteArray().Reverse(); WriteBinary(bytes, 0, bytes.Length); } /// /// Writes bytes array data into the SSH data stream. /// /// Byte array data to write. /// is . public void Write(byte[] data) { if (data is null) { throw new ArgumentNullException(nameof(data)); } Write(data, 0, data.Length); } /// /// Writes string data to the SSH data stream using the specified encoding. /// /// The string data to write. /// The character encoding to use. /// is . /// is . public void Write(string s, Encoding encoding) { if (encoding is null) { throw new ArgumentNullException(nameof(encoding)); } #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER ReadOnlySpan value = s; var count = encoding.GetByteCount(value); var bytes = count <= 256 ? stackalloc byte[count] : new byte[count]; encoding.GetBytes(value, bytes); Write((uint) count); Write(bytes); #else var bytes = encoding.GetBytes(s); WriteBinary(bytes, 0, bytes.Length); #endif } /// /// Reads a byte array from the SSH data stream. /// /// /// The byte array read from the SSH data stream. /// public byte[] ReadBinary() { var length = ReadUInt32(); if (length > int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Data longer than {0} is not supported.", int.MaxValue)); } return ReadBytes((int)length); } /// /// Writes a buffer preceded by its length into the SSH data stream. /// /// The data to write. /// is . public void WriteBinary(byte[] buffer) { if (buffer is null) { throw new ArgumentNullException(nameof(buffer)); } WriteBinary(buffer, 0, buffer.Length); } /// /// Writes a buffer preceded by its length into the SSH data stream. /// /// An array of bytes. This method write bytes from buffer to the current SSH data stream. /// The zero-based byte offset in at which to begin writing bytes to the SSH data stream. /// The number of bytes to be written to the current SSH data stream. /// is . /// The sum of and is greater than the buffer length. /// or is negative. public void WriteBinary(byte[] buffer, int offset, int count) { Write((uint) count); Write(buffer, offset, count); } /// /// Reads a from the SSH datastream. /// /// /// The read from the SSH data stream. /// public BigInteger ReadBigInt() { var length = ReadUInt32(); var data = ReadBytes((int) length); return new BigInteger(data.Reverse()); } /// /// Reads the next data type from the SSH data stream. /// /// /// The read from the SSH data stream. /// public ushort ReadUInt16() { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER Span bytes = stackalloc byte[2]; ReadBytes(bytes); return System.Buffers.Binary.BinaryPrimitives.ReadUInt16BigEndian(bytes); #else var data = ReadBytes(2); return Pack.BigEndianToUInt16(data); #endif } /// /// Reads the next data type from the SSH data stream. /// /// /// The read from the SSH data stream. /// public uint ReadUInt32() { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER Span span = stackalloc byte[4]; ReadBytes(span); return System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(span); #else var data = ReadBytes(4); return Pack.BigEndianToUInt32(data); #endif // NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER } /// /// Reads the next data type from the SSH data stream. /// /// /// The read from the SSH data stream. /// public ulong ReadUInt64() { #if NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER Span span = stackalloc byte[8]; ReadBytes(span); return System.Buffers.Binary.BinaryPrimitives.ReadUInt64BigEndian(span); #else var data = ReadBytes(8); return Pack.BigEndianToUInt64(data); #endif // NETSTANDARD2_1_OR_GREATER || NET6_0_OR_GREATER } /// /// Reads the next data type from the SSH data stream. /// /// The character encoding to use. Defaults to . /// /// The read from the SSH data stream. /// public string ReadString(Encoding encoding = null) { encoding ??= Encoding.UTF8; var length = ReadUInt32(); if (length > int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); } var bytes = ReadBytes((int) length); return encoding.GetString(bytes, 0, bytes.Length); } /// /// Writes the stream contents to a byte array, regardless of the . /// /// /// This method returns the contents of the as a byte array. /// /// /// If the current instance was constructed on a provided byte array, a copy of the section of the array /// to which this instance has access is returned. /// public override byte[] ToArray() { if (Capacity == Length) { return GetBuffer(); } return base.ToArray(); } /// /// Reads next specified number of bytes data type from internal buffer. /// /// Number of bytes to read. /// /// An array of bytes that was read from the internal buffer. /// /// is greater than the internal buffer size. internal byte[] ReadBytes(int length) { var data = new byte[length]; var bytesRead = Read(data, 0, length); if (bytesRead < length) { 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)); } return data; } #if NETSTANDARD2_1 || NET6_0_OR_GREATER /// /// Reads data into the specified . /// /// The buffer to read into. /// is larger than the total of bytes available. private void ReadBytes(Span buffer) { var bytesRead = Read(buffer); if (bytesRead < buffer.Length) { 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)); } } #endif // NETSTANDARD2_1 || NET6_0_OR_GREATER } }