瀏覽代碼

Use ArraySegment for channel data (#1650)

The library currently allocates 4 bytes (and some) for every 1 byte of file
downloaded(*). It could be 0. This takes it to 3.

(*)
1. Array allocated for read of encrypted packet from socket
2. Array for decrypted packet
3. Array for channel data (removed in this change)
4. Array for sftp data packet
Rob Hague 4 月之前
父節點
當前提交
fd05d76ad6

+ 6 - 6
src/Renci.SshNet/Channels/Channel.cs

@@ -378,9 +378,9 @@ namespace Renci.SshNet.Channels
         /// Called when channel data is received.
         /// </summary>
         /// <param name="data">The data.</param>
-        protected virtual void OnData(byte[] data)
+        protected virtual void OnData(ArraySegment<byte> data)
         {
-            AdjustDataWindow(data);
+            AdjustDataWindow(data.Count);
 
             DataReceived?.Invoke(this, new ChannelDataEventArgs(LocalChannelNumber, data));
         }
@@ -392,7 +392,7 @@ namespace Renci.SshNet.Channels
         /// <param name="dataTypeCode">The data type code.</param>
         protected virtual void OnExtendedData(byte[] data, uint dataTypeCode)
         {
-            AdjustDataWindow(data);
+            AdjustDataWindow(data.Length);
 
             ExtendedDataReceived?.Invoke(this, new ChannelExtendedDataEventArgs(LocalChannelNumber, data, dataTypeCode));
         }
@@ -651,7 +651,7 @@ namespace Renci.SshNet.Channels
             {
                 try
                 {
-                    OnData(e.Message.Data);
+                    OnData(new ArraySegment<byte>(e.Message.Data, e.Message.Offset, e.Message.Size));
                 }
                 catch (Exception ex)
                 {
@@ -768,9 +768,9 @@ namespace Renci.SshNet.Channels
             }
         }
 
-        private void AdjustDataWindow(byte[] messageData)
+        private void AdjustDataWindow(int count)
         {
-            LocalWindowSize -= (uint)messageData.Length;
+            LocalWindowSize -= (uint)count;
 
             // Adjust window if window size is too low
             if (LocalWindowSize < LocalPacketSize)

+ 2 - 2
src/Renci.SshNet/Channels/ChannelDirectTcpip.cs

@@ -194,7 +194,7 @@ namespace Renci.SshNet.Channels
         /// Called when channel data is received.
         /// </summary>
         /// <param name="data">The data.</param>
-        protected override void OnData(byte[] data)
+        protected override void OnData(ArraySegment<byte> data)
         {
             base.OnData(data);
 
@@ -204,7 +204,7 @@ namespace Renci.SshNet.Channels
                 {
                     if (_socket.IsConnected())
                     {
-                        SocketAbstraction.Send(_socket, data, 0, data.Length);
+                        SocketAbstraction.Send(_socket, data.Array, data.Offset, data.Count);
                     }
                 }
             }

+ 2 - 2
src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs

@@ -201,14 +201,14 @@ namespace Renci.SshNet.Channels
         /// Called when channel data is received.
         /// </summary>
         /// <param name="data">The data.</param>
-        protected override void OnData(byte[] data)
+        protected override void OnData(ArraySegment<byte> data)
         {
             base.OnData(data);
 
             var socket = _socket;
             if (socket.IsConnected())
             {
-                SocketAbstraction.Send(socket, data, 0, data.Length);
+                SocketAbstraction.Send(socket, data.Array, data.Offset, data.Count);
             }
         }
     }

+ 8 - 3
src/Renci.SshNet/Common/ChannelDataEventArgs.cs

@@ -13,17 +13,22 @@ namespace Renci.SshNet.Common
         /// <param name="channelNumber">Channel number.</param>
         /// <param name="data">Channel data.</param>
         /// <exception cref="ArgumentNullException"><paramref name="data"/> is <see langword="null"/>.</exception>
-        public ChannelDataEventArgs(uint channelNumber, byte[] data)
+        public ChannelDataEventArgs(uint channelNumber, ArraySegment<byte> data)
             : base(channelNumber)
         {
-            ThrowHelper.ThrowIfNull(data);
+            ThrowHelper.ThrowIfNull(data.Array);
 
             Data = data;
         }
 
+        internal ChannelDataEventArgs(uint channelNumber, byte[] data)
+            : this(channelNumber, new ArraySegment<byte>(data))
+        {
+        }
+
         /// <summary>
         /// Gets channel data.
         /// </summary>
-        public byte[] Data { get; }
+        public ArraySegment<byte> Data { get; }
     }
 }

+ 4 - 2
src/Renci.SshNet/Common/ChannelExtendedDataEventArgs.cs

@@ -1,4 +1,6 @@
-namespace Renci.SshNet.Common
+using System;
+
+namespace Renci.SshNet.Common
 {
     /// <summary>
     /// Provides data for <see cref="Channels.Channel.ExtendedDataReceived"/> events.
@@ -12,7 +14,7 @@
         /// <param name="data">Channel data.</param>
         /// <param name="dataTypeCode">Channel data type code.</param>
         public ChannelExtendedDataEventArgs(uint channelNumber, byte[] data, uint dataTypeCode)
-            : base(channelNumber, data)
+            : base(channelNumber, new ArraySegment<byte>(data))
         {
             DataTypeCode = dataTypeCode;
         }

+ 15 - 1
src/Renci.SshNet/Common/SshData.cs

@@ -232,7 +232,7 @@ namespace Renci.SshNet.Common
         }
 
         /// <summary>
-        /// Reads next data type as byte array from internal buffer.
+        /// Reads a length-prefixed byte array from the internal buffer.
         /// </summary>
         /// <returns>
         /// The bytes read.
@@ -242,6 +242,20 @@ namespace Renci.SshNet.Common
             return _stream.ReadBinary();
         }
 
+        /// <summary>
+        /// Reads a length-prefixed byte array from the internal buffer,
+        /// returned as a view over the buffer.
+        /// </summary>
+        /// <remarks>
+        /// When using this method, consider whether the underlying buffer is shared
+        /// or reused, and whether the returned <see cref="ArraySegment{T}"/> may
+        /// exist beyond the lifetime for which it is valid to be used.
+        /// </remarks>
+        private protected ArraySegment<byte> ReadBinarySegment()
+        {
+            return _stream.ReadBinarySegment();
+        }
+
         /// <summary>
         /// Reads next name-list data type from internal buffer.
         /// </summary>

+ 5 - 3
src/Renci.SshNet/Messages/Connection/ChannelDataMessage.cs

@@ -120,9 +120,11 @@ namespace Renci.SshNet.Messages.Connection
         {
             base.LoadData();
 
-            Data = ReadBinary();
-            Offset = 0;
-            Size = Data.Length;
+            var data = ReadBinarySegment();
+
+            Data = data.Array;
+            Offset = data.Offset;
+            Size = data.Count;
         }
 
         /// <summary>

+ 2 - 2
src/Renci.SshNet/Netconf/NetConfSession.cs

@@ -121,9 +121,9 @@ namespace Renci.SshNet.NetConf
             WaitOnHandle(_serverCapabilitiesConfirmed, OperationTimeout);
         }
 
-        protected override void OnDataReceived(byte[] data)
+        protected override void OnDataReceived(ArraySegment<byte> data)
         {
-            var chunk = Encoding.UTF8.GetString(data);
+            var chunk = Encoding.UTF8.GetString(data.Array, data.Offset, data.Count);
 
             if (ServerCapabilities is null)
             {

+ 6 - 6
src/Renci.SshNet/ScpClient.cs

@@ -257,7 +257,7 @@ namespace Renci.SshNet
             using (var input = ServiceFactory.CreatePipeStream())
             using (var channel = Session.CreateChannelSession())
             {
-                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
+                channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
                 channel.Closed += (sender, e) => input.Dispose();
                 channel.Open();
 
@@ -300,7 +300,7 @@ namespace Renci.SshNet
             using (var input = ServiceFactory.CreatePipeStream())
             using (var channel = Session.CreateChannelSession())
             {
-                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
+                channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
                 channel.Closed += (sender, e) => input.Dispose();
                 channel.Open();
 
@@ -346,7 +346,7 @@ namespace Renci.SshNet
             using (var input = ServiceFactory.CreatePipeStream())
             using (var channel = Session.CreateChannelSession())
             {
-                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
+                channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
                 channel.Closed += (sender, e) => input.Dispose();
                 channel.Open();
 
@@ -389,7 +389,7 @@ namespace Renci.SshNet
             using (var input = ServiceFactory.CreatePipeStream())
             using (var channel = Session.CreateChannelSession())
             {
-                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
+                channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
                 channel.Closed += (sender, e) => input.Dispose();
                 channel.Open();
 
@@ -429,7 +429,7 @@ namespace Renci.SshNet
             using (var input = ServiceFactory.CreatePipeStream())
             using (var channel = Session.CreateChannelSession())
             {
-                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
+                channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
                 channel.Closed += (sender, e) => input.Dispose();
                 channel.Open();
 
@@ -469,7 +469,7 @@ namespace Renci.SshNet
             using (var input = ServiceFactory.CreatePipeStream())
             using (var channel = Session.CreateChannelSession())
             {
-                channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
+                channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
                 channel.Closed += (sender, e) => input.Dispose();
                 channel.Open();
 

+ 17 - 19
src/Renci.SshNet/Sftp/SftpSession.cs

@@ -302,38 +302,36 @@ namespace Renci.SshNet.Sftp
             WorkingDirectory = RequestRealPath(".")[0].Key;
         }
 
-        protected override void OnDataReceived(byte[] data)
+        protected override void OnDataReceived(ArraySegment<byte> data)
         {
-            ArraySegment<byte> d = new(data);
-
             // If the buffer is empty then skip a copy and read packets
             // directly out of the given data.
             if (_buffer.ActiveLength == 0)
             {
-                while (d.Count >= 4)
+                while (data.Count >= 4)
                 {
-                    var packetLength = BinaryPrimitives.ReadInt32BigEndian(d);
+                    var packetLength = BinaryPrimitives.ReadInt32BigEndian(data);
 
-                    if (d.Count - 4 < packetLength)
+                    if (data.Count - 4 < packetLength)
                     {
                         break;
                     }
 
-                    if (!TryLoadSftpMessage(d.Slice(4, packetLength)))
+                    if (!TryLoadSftpMessage(data.Slice(4, packetLength)))
                     {
                         // An error occured.
                         return;
                     }
 
-                    d = d.Slice(4 + packetLength);
+                    data = data.Slice(4 + packetLength);
                 }
 
-                if (d.Count > 0)
+                if (data.Count > 0)
                 {
                     // Now buffer the remainder.
-                    _buffer.EnsureAvailableSpace(d.Count);
-                    d.AsSpan().CopyTo(_buffer.AvailableSpan);
-                    _buffer.Commit(d.Count);
+                    _buffer.EnsureAvailableSpace(data.Count);
+                    data.AsSpan().CopyTo(_buffer.AvailableSpan);
+                    _buffer.Commit(data.Count);
                 }
 
                 return;
@@ -341,20 +339,20 @@ namespace Renci.SshNet.Sftp
 
             // The buffer already had some data. Append the new data and
             // proceed with reading out packets.
-            _buffer.EnsureAvailableSpace(d.Count);
-            d.AsSpan().CopyTo(_buffer.AvailableSpan);
-            _buffer.Commit(d.Count);
+            _buffer.EnsureAvailableSpace(data.Count);
+            data.AsSpan().CopyTo(_buffer.AvailableSpan);
+            _buffer.Commit(data.Count);
 
             while (_buffer.ActiveLength >= 4)
             {
-                d = new ArraySegment<byte>(
+                data = new ArraySegment<byte>(
                     _buffer.DangerousGetUnderlyingBuffer(),
                     _buffer.ActiveStartOffset,
                     _buffer.ActiveLength);
 
-                var packetLength = BinaryPrimitives.ReadInt32BigEndian(d);
+                var packetLength = BinaryPrimitives.ReadInt32BigEndian(data);
 
-                if (d.Count - 4 < packetLength)
+                if (data.Count - 4 < packetLength)
                 {
                     break;
                 }
@@ -362,7 +360,7 @@ namespace Renci.SshNet.Sftp
                 // Note: the packet data in the buffer is safe to read from
                 // only for the duration of this load. If it needs to be stored,
                 // callees should make their own copy.
-                _ = TryLoadSftpMessage(d.Slice(4, packetLength));
+                _ = TryLoadSftpMessage(data.Slice(4, packetLength));
 
                 _buffer.Discard(4 + packetLength);
             }

+ 2 - 2
src/Renci.SshNet/Shell.cs

@@ -241,12 +241,12 @@ namespace Renci.SshNet
 
         private void Channel_ExtendedDataReceived(object sender, ChannelExtendedDataEventArgs e)
         {
-            _extendedOutputStream?.Write(e.Data, 0, e.Data.Length);
+            _extendedOutputStream?.Write(e.Data.Array, e.Data.Offset, e.Data.Count);
         }
 
         private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
         {
-            _outputStream?.Write(e.Data, 0, e.Data.Length);
+            _outputStream?.Write(e.Data.Array, e.Data.Offset, e.Data.Count);
         }
 
         private void Channel_Closed(object sender, ChannelEventArgs e)

+ 3 - 3
src/Renci.SshNet/ShellStream.cs

@@ -965,16 +965,16 @@ namespace Renci.SshNet
         {
             lock (_sync)
             {
-                _readBuffer.EnsureAvailableSpace(e.Data.Length);
+                _readBuffer.EnsureAvailableSpace(e.Data.Count);
 
                 e.Data.AsSpan().CopyTo(_readBuffer.AvailableSpan);
 
-                _readBuffer.Commit(e.Data.Length);
+                _readBuffer.Commit(e.Data.Count);
 
                 Monitor.PulseAll(_sync);
             }
 
-            DataReceived?.Invoke(this, new ShellDataEventArgs(e.Data));
+            DataReceived?.Invoke(this, new ShellDataEventArgs(e.Data.ToArray()));
         }
     }
 }

+ 2 - 2
src/Renci.SshNet/SshCommand.cs

@@ -583,7 +583,7 @@ namespace Renci.SshNet
 
         private void Channel_ExtendedDataReceived(object? sender, ChannelExtendedDataEventArgs e)
         {
-            ExtendedOutputStream.Write(e.Data, 0, e.Data.Length);
+            ExtendedOutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
 
             if (e.DataTypeCode == 1)
             {
@@ -593,7 +593,7 @@ namespace Renci.SshNet
 
         private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
         {
-            OutputStream.Write(e.Data, 0, e.Data.Length);
+            OutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
         }
 
         /// <summary>

+ 1 - 1
src/Renci.SshNet/SubsystemSession.cs

@@ -173,7 +173,7 @@ namespace Renci.SshNet
         /// Called when data is received.
         /// </summary>
         /// <param name="data">The data.</param>
-        protected abstract void OnDataReceived(byte[] data);
+        protected abstract void OnDataReceived(ArraySegment<byte> data);
 
         /// <summary>
         /// Raises the error.

+ 1 - 1
test/Renci.SshNet.Tests/Classes/Channels/ChannelStub.cs

@@ -68,7 +68,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             }
         }
 
-        protected override void OnData(byte[] data)
+        protected override void OnData(ArraySegment<byte> data)
         {
             base.OnData(data);
 

+ 1 - 1
test/Renci.SshNet.Tests/Classes/Channels/ClientChannelStub.cs

@@ -72,7 +72,7 @@ namespace Renci.SshNet.Tests.Classes.Channels
             }
         }
 
-        protected override void OnData(byte[] data)
+        protected override void OnData(ArraySegment<byte> data)
         {
             base.OnData(data);
 

+ 1 - 3
test/Renci.SshNet.Tests/Classes/Messages/Connection/ChannelDataMessageTest.cs

@@ -145,9 +145,7 @@ namespace Renci.SshNet.Tests.Classes.Messages.Connection
 
             target.Load(bytes, 1, bytes.Length - 1); // skip message type
 
-            Assert.IsTrue(target.Data.SequenceEqual(data.Take(offset, size)));
-            Assert.AreEqual(0, target.Offset);
-            Assert.AreEqual(size, target.Size);
+            CollectionAssert.AreEqual(data.Take(offset, size), target.Data.Take(target.Offset, target.Size));
         }
     }
 }

+ 1 - 1
test/Renci.SshNet.Tests/Classes/SubsystemSessionStub.cs

@@ -37,7 +37,7 @@ namespace Renci.SshNet.Tests.Classes
             }
         }
 
-        protected override void OnDataReceived(byte[] data)
+        protected override void OnDataReceived(ArraySegment<byte> data)
         {
             OnDataReceivedInvocations.Add(new ChannelDataEventArgs(0, data));