Skip to content

Use ArraySegment for channel data #1650

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/Renci.SshNet/Channels/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -378,9 +378,9 @@ protected virtual void OnWindowAdjust(uint bytesToAdd)
/// 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));
}
Expand All @@ -392,7 +392,7 @@ protected virtual void OnData(byte[] data)
/// <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));
}
Expand Down Expand Up @@ -651,7 +651,7 @@ private void OnChannelData(object sender, MessageEventArgs<ChannelDataMessage> e
{
try
{
OnData(e.Message.Data);
OnData(new ArraySegment<byte>(e.Message.Data, e.Message.Offset, e.Message.Size));
}
catch (Exception ex)
{
Expand Down Expand Up @@ -768,9 +768,9 @@ private void OnChannelFailure(object sender, MessageEventArgs<ChannelFailureMess
}
}

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)
Expand Down
4 changes: 2 additions & 2 deletions src/Renci.SshNet/Channels/ChannelDirectTcpip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ protected override void Close()
/// 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);

Expand All @@ -204,7 +204,7 @@ protected override void OnData(byte[] data)
{
if (_socket.IsConnected())
{
SocketAbstraction.Send(_socket, data, 0, data.Length);
SocketAbstraction.Send(_socket, data.Array, data.Offset, data.Count);
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,14 @@ protected override void Close()
/// 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);
}
}
}
Expand Down
11 changes: 8 additions & 3 deletions src/Renci.SshNet/Common/ChannelDataEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,22 @@ internal class ChannelDataEventArgs : ChannelEventArgs
/// <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; }
}
}
6 changes: 4 additions & 2 deletions src/Renci.SshNet/Common/ChannelExtendedDataEventArgs.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace Renci.SshNet.Common
using System;

namespace Renci.SshNet.Common
{
/// <summary>
/// Provides data for <see cref="Channels.Channel.ExtendedDataReceived"/> events.
Expand All @@ -12,7 +14,7 @@ internal sealed class ChannelExtendedDataEventArgs : ChannelDataEventArgs
/// <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;
}
Expand Down
16 changes: 15 additions & 1 deletion src/Renci.SshNet/Common/SshData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ protected string ReadString(Encoding encoding = null)
}

/// <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.
Expand All @@ -242,6 +242,20 @@ protected byte[] ReadBinary()
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>
Expand Down
8 changes: 5 additions & 3 deletions src/Renci.SshNet/Messages/Connection/ChannelDataMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,11 @@ protected override void LoadData()
{
base.LoadData();

Data = ReadBinary();
Offset = 0;
Size = Data.Length;
var data = ReadBinarySegment();

Data = data.Array;
Offset = data.Offset;
Size = data.Count;
}

/// <summary>
Expand Down
4 changes: 2 additions & 2 deletions src/Renci.SshNet/Netconf/NetConfSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ protected override void OnChannelOpen()
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)
{
Expand Down
12 changes: 6 additions & 6 deletions src/Renci.SshNet/ScpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ public void Upload(Stream source, string path)
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();

Expand Down Expand Up @@ -300,7 +300,7 @@ public void Upload(FileInfo fileInfo, string path)
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();

Expand Down Expand Up @@ -346,7 +346,7 @@ public void Upload(DirectoryInfo directoryInfo, string path)
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();

Expand Down Expand Up @@ -389,7 +389,7 @@ public void Download(string filename, FileInfo fileInfo)
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();

Expand Down Expand Up @@ -429,7 +429,7 @@ public void Download(string directoryName, DirectoryInfo directoryInfo)
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();

Expand Down Expand Up @@ -469,7 +469,7 @@ public void Download(string filename, Stream destination)
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();

Expand Down
36 changes: 17 additions & 19 deletions src/Renci.SshNet/Sftp/SftpSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,67 +302,65 @@ protected override void OnChannelOpen()
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;
}

// 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;
}

// 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);
}
Expand Down
4 changes: 2 additions & 2 deletions src/Renci.SshNet/Shell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -241,12 +241,12 @@ private void Session_Disconnected(object sender, EventArgs e)

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)
Expand Down
6 changes: 3 additions & 3 deletions src/Renci.SshNet/ShellStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -965,16 +965,16 @@ private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
{
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()));
}
}
}
4 changes: 2 additions & 2 deletions src/Renci.SshNet/SshCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ private void Channel_RequestReceived(object? sender, ChannelRequestEventArgs e)

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)
{
Expand All @@ -593,7 +593,7 @@ private void Channel_ExtendedDataReceived(object? sender, ChannelExtendedDataEve

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>
Expand Down
2 changes: 1 addition & 1 deletion src/Renci.SshNet/SubsystemSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ public void SendData(byte[] data)
/// 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.
Expand Down
2 changes: 1 addition & 1 deletion test/Renci.SshNet.Tests/Classes/Channels/ChannelStub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected override void OnClose()
}
}

protected override void OnData(byte[] data)
protected override void OnData(ArraySegment<byte> data)
{
base.OnData(data);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected override void OnClose()
}
}

protected override void OnData(byte[] data)
protected override void OnData(ArraySegment<byte> data)
{
base.OnData(data);

Expand Down
Loading