using System; using System.Globalization; using System.IO; using System.Numerics; 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; } } #if NETFRAMEWORK || NETSTANDARD2_0 private int Read(Span buffer) { var sharedBuffer = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); var numRead = Read(sharedBuffer, 0, buffer.Length); sharedBuffer.AsSpan(0, numRead).CopyTo(buffer); System.Buffers.ArrayPool.Shared.Return(sharedBuffer); return numRead; } private void Write(ReadOnlySpan buffer) { var sharedBuffer = System.Buffers.ArrayPool.Shared.Rent(buffer.Length); buffer.CopyTo(sharedBuffer); Write(sharedBuffer, 0, buffer.Length); System.Buffers.ArrayPool.Shared.Return(sharedBuffer); } #endif /// /// Writes an to the SSH data stream. /// /// data to write. public void Write(uint value) { Span bytes = stackalloc byte[4]; System.Buffers.Binary.BinaryPrimitives.WriteUInt32BigEndian(bytes, value); Write(bytes); } /// /// Writes an to the SSH data stream. /// /// data to write. public void Write(ulong value) { Span bytes = stackalloc byte[8]; System.Buffers.Binary.BinaryPrimitives.WriteUInt64BigEndian(bytes, value); Write(bytes); } /// /// Writes a into the SSH data stream. /// /// The to write. public void Write(BigInteger data) { var bytes = data.ToByteArray(isBigEndian: true); 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) { ThrowHelper.ThrowIfNull(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) { ThrowHelper.ThrowIfNull(encoding); #if NETSTANDARD2_1 || NET 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) { ThrowHelper.ThrowIfNull(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 data = ReadBinary(); #if NETSTANDARD2_1 || NET return new BigInteger(data, isBigEndian: true); #else return new BigInteger(data.Reverse()); #endif } /// /// Reads the next data type from the SSH data stream. /// /// /// The read from the SSH data stream. /// public ushort ReadUInt16() { Span bytes = stackalloc byte[2]; ReadBytes(bytes); return System.Buffers.Binary.BinaryPrimitives.ReadUInt16BigEndian(bytes); } /// /// Reads the next data type from the SSH data stream. /// /// /// The read from the SSH data stream. /// public uint ReadUInt32() { Span span = stackalloc byte[4]; ReadBytes(span); return System.Buffers.Binary.BinaryPrimitives.ReadUInt32BigEndian(span); } /// /// Reads the next data type from the SSH data stream. /// /// /// The read from the SSH data stream. /// public ulong ReadUInt64() { Span span = stackalloc byte[8]; ReadBytes(span); return System.Buffers.Binary.BinaryPrimitives.ReadUInt64BigEndian(span); } /// /// 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; } /// /// 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)); } } } }