Skip to content

Commit 4f83427

Browse files
committed
Use ArraySegment for channel data
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
1 parent 1f1a5fe commit 4f83427

18 files changed

+79
-60
lines changed

src/Renci.SshNet/Channels/Channel.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -378,9 +378,9 @@ protected virtual void OnWindowAdjust(uint bytesToAdd)
378378
/// Called when channel data is received.
379379
/// </summary>
380380
/// <param name="data">The data.</param>
381-
protected virtual void OnData(byte[] data)
381+
protected virtual void OnData(ArraySegment<byte> data)
382382
{
383-
AdjustDataWindow(data);
383+
AdjustDataWindow(data.Count);
384384

385385
DataReceived?.Invoke(this, new ChannelDataEventArgs(LocalChannelNumber, data));
386386
}
@@ -392,7 +392,7 @@ protected virtual void OnData(byte[] data)
392392
/// <param name="dataTypeCode">The data type code.</param>
393393
protected virtual void OnExtendedData(byte[] data, uint dataTypeCode)
394394
{
395-
AdjustDataWindow(data);
395+
AdjustDataWindow(data.Length);
396396

397397
ExtendedDataReceived?.Invoke(this, new ChannelExtendedDataEventArgs(LocalChannelNumber, data, dataTypeCode));
398398
}
@@ -651,7 +651,7 @@ private void OnChannelData(object sender, MessageEventArgs<ChannelDataMessage> e
651651
{
652652
try
653653
{
654-
OnData(e.Message.Data);
654+
OnData(new ArraySegment<byte>(e.Message.Data, e.Message.Offset, e.Message.Size));
655655
}
656656
catch (Exception ex)
657657
{
@@ -768,9 +768,9 @@ private void OnChannelFailure(object sender, MessageEventArgs<ChannelFailureMess
768768
}
769769
}
770770

771-
private void AdjustDataWindow(byte[] messageData)
771+
private void AdjustDataWindow(int count)
772772
{
773-
LocalWindowSize -= (uint)messageData.Length;
773+
LocalWindowSize -= (uint)count;
774774

775775
// Adjust window if window size is too low
776776
if (LocalWindowSize < LocalPacketSize)

src/Renci.SshNet/Channels/ChannelDirectTcpip.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ protected override void Close()
194194
/// Called when channel data is received.
195195
/// </summary>
196196
/// <param name="data">The data.</param>
197-
protected override void OnData(byte[] data)
197+
protected override void OnData(ArraySegment<byte> data)
198198
{
199199
base.OnData(data);
200200

@@ -204,7 +204,7 @@ protected override void OnData(byte[] data)
204204
{
205205
if (_socket.IsConnected())
206206
{
207-
SocketAbstraction.Send(_socket, data, 0, data.Length);
207+
SocketAbstraction.Send(_socket, data.Array, data.Offset, data.Count);
208208
}
209209
}
210210
}

src/Renci.SshNet/Channels/ChannelForwardedTcpip.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,14 +201,14 @@ protected override void Close()
201201
/// Called when channel data is received.
202202
/// </summary>
203203
/// <param name="data">The data.</param>
204-
protected override void OnData(byte[] data)
204+
protected override void OnData(ArraySegment<byte> data)
205205
{
206206
base.OnData(data);
207207

208208
var socket = _socket;
209209
if (socket.IsConnected())
210210
{
211-
SocketAbstraction.Send(socket, data, 0, data.Length);
211+
SocketAbstraction.Send(socket, data.Array, data.Offset, data.Count);
212212
}
213213
}
214214
}

src/Renci.SshNet/Common/ChannelDataEventArgs.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,22 @@ internal class ChannelDataEventArgs : ChannelEventArgs
1313
/// <param name="channelNumber">Channel number.</param>
1414
/// <param name="data">Channel data.</param>
1515
/// <exception cref="ArgumentNullException"><paramref name="data"/> is <see langword="null"/>.</exception>
16-
public ChannelDataEventArgs(uint channelNumber, byte[] data)
16+
public ChannelDataEventArgs(uint channelNumber, ArraySegment<byte> data)
1717
: base(channelNumber)
1818
{
19-
ThrowHelper.ThrowIfNull(data);
19+
ThrowHelper.ThrowIfNull(data.Array);
2020

2121
Data = data;
2222
}
2323

24+
internal ChannelDataEventArgs(uint channelNumber, byte[] data)
25+
: this(channelNumber, new ArraySegment<byte>(data))
26+
{
27+
}
28+
2429
/// <summary>
2530
/// Gets channel data.
2631
/// </summary>
27-
public byte[] Data { get; }
32+
public ArraySegment<byte> Data { get; }
2833
}
2934
}

src/Renci.SshNet/Common/ChannelExtendedDataEventArgs.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Renci.SshNet.Common
1+
using System;
2+
3+
namespace Renci.SshNet.Common
24
{
35
/// <summary>
46
/// Provides data for <see cref="Channels.Channel.ExtendedDataReceived"/> events.
@@ -12,7 +14,7 @@ internal sealed class ChannelExtendedDataEventArgs : ChannelDataEventArgs
1214
/// <param name="data">Channel data.</param>
1315
/// <param name="dataTypeCode">Channel data type code.</param>
1416
public ChannelExtendedDataEventArgs(uint channelNumber, byte[] data, uint dataTypeCode)
15-
: base(channelNumber, data)
17+
: base(channelNumber, new ArraySegment<byte>(data))
1618
{
1719
DataTypeCode = dataTypeCode;
1820
}

src/Renci.SshNet/Common/SshData.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ protected string ReadString(Encoding encoding = null)
232232
}
233233

234234
/// <summary>
235-
/// Reads next data type as byte array from internal buffer.
235+
/// Reads a length-prefixed byte array from the internal buffer.
236236
/// </summary>
237237
/// <returns>
238238
/// The bytes read.
@@ -242,6 +242,20 @@ protected byte[] ReadBinary()
242242
return _stream.ReadBinary();
243243
}
244244

245+
/// <summary>
246+
/// Reads a length-prefixed byte array from the internal buffer,
247+
/// returned as a view over the buffer.
248+
/// </summary>
249+
/// <remarks>
250+
/// When using this method, consider whether the underlying buffer is shared
251+
/// or reused, and whether the returned <see cref="ArraySegment{T}"/> may
252+
/// exist beyond the lifetime for which it is valid to be used.
253+
/// </remarks>
254+
private protected ArraySegment<byte> ReadBinarySegment()
255+
{
256+
return _stream.ReadBinarySegment();
257+
}
258+
245259
/// <summary>
246260
/// Reads next name-list data type from internal buffer.
247261
/// </summary>

src/Renci.SshNet/Messages/Connection/ChannelDataMessage.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,11 @@ protected override void LoadData()
120120
{
121121
base.LoadData();
122122

123-
Data = ReadBinary();
124-
Offset = 0;
125-
Size = Data.Length;
123+
var data = ReadBinarySegment();
124+
125+
Data = data.Array;
126+
Offset = data.Offset;
127+
Size = data.Count;
126128
}
127129

128130
/// <summary>

src/Renci.SshNet/Netconf/NetConfSession.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,9 @@ protected override void OnChannelOpen()
121121
WaitOnHandle(_serverCapabilitiesConfirmed, OperationTimeout);
122122
}
123123

124-
protected override void OnDataReceived(byte[] data)
124+
protected override void OnDataReceived(ArraySegment<byte> data)
125125
{
126-
var chunk = Encoding.UTF8.GetString(data);
126+
var chunk = Encoding.UTF8.GetString(data.Array, data.Offset, data.Count);
127127

128128
if (ServerCapabilities is null)
129129
{

src/Renci.SshNet/ScpClient.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ public void Upload(Stream source, string path)
257257
using (var input = ServiceFactory.CreatePipeStream())
258258
using (var channel = Session.CreateChannelSession())
259259
{
260-
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
260+
channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
261261
channel.Closed += (sender, e) => input.Dispose();
262262
channel.Open();
263263

@@ -300,7 +300,7 @@ public void Upload(FileInfo fileInfo, string path)
300300
using (var input = ServiceFactory.CreatePipeStream())
301301
using (var channel = Session.CreateChannelSession())
302302
{
303-
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
303+
channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
304304
channel.Closed += (sender, e) => input.Dispose();
305305
channel.Open();
306306

@@ -346,7 +346,7 @@ public void Upload(DirectoryInfo directoryInfo, string path)
346346
using (var input = ServiceFactory.CreatePipeStream())
347347
using (var channel = Session.CreateChannelSession())
348348
{
349-
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
349+
channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
350350
channel.Closed += (sender, e) => input.Dispose();
351351
channel.Open();
352352

@@ -389,7 +389,7 @@ public void Download(string filename, FileInfo fileInfo)
389389
using (var input = ServiceFactory.CreatePipeStream())
390390
using (var channel = Session.CreateChannelSession())
391391
{
392-
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
392+
channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
393393
channel.Closed += (sender, e) => input.Dispose();
394394
channel.Open();
395395

@@ -429,7 +429,7 @@ public void Download(string directoryName, DirectoryInfo directoryInfo)
429429
using (var input = ServiceFactory.CreatePipeStream())
430430
using (var channel = Session.CreateChannelSession())
431431
{
432-
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
432+
channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
433433
channel.Closed += (sender, e) => input.Dispose();
434434
channel.Open();
435435

@@ -469,7 +469,7 @@ public void Download(string filename, Stream destination)
469469
using (var input = ServiceFactory.CreatePipeStream())
470470
using (var channel = Session.CreateChannelSession())
471471
{
472-
channel.DataReceived += (sender, e) => input.Write(e.Data, 0, e.Data.Length);
472+
channel.DataReceived += (sender, e) => input.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
473473
channel.Closed += (sender, e) => input.Dispose();
474474
channel.Open();
475475

src/Renci.SshNet/Sftp/SftpSession.cs

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -302,67 +302,65 @@ protected override void OnChannelOpen()
302302
WorkingDirectory = RequestRealPath(".")[0].Key;
303303
}
304304

305-
protected override void OnDataReceived(byte[] data)
305+
protected override void OnDataReceived(ArraySegment<byte> data)
306306
{
307-
ArraySegment<byte> d = new(data);
308-
309307
// If the buffer is empty then skip a copy and read packets
310308
// directly out of the given data.
311309
if (_buffer.ActiveLength == 0)
312310
{
313-
while (d.Count >= 4)
311+
while (data.Count >= 4)
314312
{
315-
var packetLength = BinaryPrimitives.ReadInt32BigEndian(d);
313+
var packetLength = BinaryPrimitives.ReadInt32BigEndian(data);
316314

317-
if (d.Count - 4 < packetLength)
315+
if (data.Count - 4 < packetLength)
318316
{
319317
break;
320318
}
321319

322-
if (!TryLoadSftpMessage(d.Slice(4, packetLength)))
320+
if (!TryLoadSftpMessage(data.Slice(4, packetLength)))
323321
{
324322
// An error occured.
325323
return;
326324
}
327325

328-
d = d.Slice(4 + packetLength);
326+
data = data.Slice(4 + packetLength);
329327
}
330328

331-
if (d.Count > 0)
329+
if (data.Count > 0)
332330
{
333331
// Now buffer the remainder.
334-
_buffer.EnsureAvailableSpace(d.Count);
335-
d.AsSpan().CopyTo(_buffer.AvailableSpan);
336-
_buffer.Commit(d.Count);
332+
_buffer.EnsureAvailableSpace(data.Count);
333+
data.AsSpan().CopyTo(_buffer.AvailableSpan);
334+
_buffer.Commit(data.Count);
337335
}
338336

339337
return;
340338
}
341339

342340
// The buffer already had some data. Append the new data and
343341
// proceed with reading out packets.
344-
_buffer.EnsureAvailableSpace(d.Count);
345-
d.AsSpan().CopyTo(_buffer.AvailableSpan);
346-
_buffer.Commit(d.Count);
342+
_buffer.EnsureAvailableSpace(data.Count);
343+
data.AsSpan().CopyTo(_buffer.AvailableSpan);
344+
_buffer.Commit(data.Count);
347345

348346
while (_buffer.ActiveLength >= 4)
349347
{
350-
d = new ArraySegment<byte>(
348+
data = new ArraySegment<byte>(
351349
_buffer.DangerousGetUnderlyingBuffer(),
352350
_buffer.ActiveStartOffset,
353351
_buffer.ActiveLength);
354352

355-
var packetLength = BinaryPrimitives.ReadInt32BigEndian(d);
353+
var packetLength = BinaryPrimitives.ReadInt32BigEndian(data);
356354

357-
if (d.Count - 4 < packetLength)
355+
if (data.Count - 4 < packetLength)
358356
{
359357
break;
360358
}
361359

362360
// Note: the packet data in the buffer is safe to read from
363361
// only for the duration of this load. If it needs to be stored,
364362
// callees should make their own copy.
365-
_ = TryLoadSftpMessage(d.Slice(4, packetLength));
363+
_ = TryLoadSftpMessage(data.Slice(4, packetLength));
366364

367365
_buffer.Discard(4 + packetLength);
368366
}

src/Renci.SshNet/Shell.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,12 @@ private void Session_Disconnected(object sender, EventArgs e)
241241

242242
private void Channel_ExtendedDataReceived(object sender, ChannelExtendedDataEventArgs e)
243243
{
244-
_extendedOutputStream?.Write(e.Data, 0, e.Data.Length);
244+
_extendedOutputStream?.Write(e.Data.Array, e.Data.Offset, e.Data.Count);
245245
}
246246

247247
private void Channel_DataReceived(object sender, ChannelDataEventArgs e)
248248
{
249-
_outputStream?.Write(e.Data, 0, e.Data.Length);
249+
_outputStream?.Write(e.Data.Array, e.Data.Offset, e.Data.Count);
250250
}
251251

252252
private void Channel_Closed(object sender, ChannelEventArgs e)

src/Renci.SshNet/ShellStream.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -965,16 +965,16 @@ private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
965965
{
966966
lock (_sync)
967967
{
968-
_readBuffer.EnsureAvailableSpace(e.Data.Length);
968+
_readBuffer.EnsureAvailableSpace(e.Data.Count);
969969

970970
e.Data.AsSpan().CopyTo(_readBuffer.AvailableSpan);
971971

972-
_readBuffer.Commit(e.Data.Length);
972+
_readBuffer.Commit(e.Data.Count);
973973

974974
Monitor.PulseAll(_sync);
975975
}
976976

977-
DataReceived?.Invoke(this, new ShellDataEventArgs(e.Data));
977+
DataReceived?.Invoke(this, new ShellDataEventArgs(e.Data.ToArray()));
978978
}
979979
}
980980
}

src/Renci.SshNet/SshCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ private void Channel_RequestReceived(object? sender, ChannelRequestEventArgs e)
583583

584584
private void Channel_ExtendedDataReceived(object? sender, ChannelExtendedDataEventArgs e)
585585
{
586-
ExtendedOutputStream.Write(e.Data, 0, e.Data.Length);
586+
ExtendedOutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
587587

588588
if (e.DataTypeCode == 1)
589589
{
@@ -593,7 +593,7 @@ private void Channel_ExtendedDataReceived(object? sender, ChannelExtendedDataEve
593593

594594
private void Channel_DataReceived(object? sender, ChannelDataEventArgs e)
595595
{
596-
OutputStream.Write(e.Data, 0, e.Data.Length);
596+
OutputStream.Write(e.Data.Array!, e.Data.Offset, e.Data.Count);
597597
}
598598

599599
/// <summary>

src/Renci.SshNet/SubsystemSession.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ public void SendData(byte[] data)
173173
/// Called when data is received.
174174
/// </summary>
175175
/// <param name="data">The data.</param>
176-
protected abstract void OnDataReceived(byte[] data);
176+
protected abstract void OnDataReceived(ArraySegment<byte> data);
177177

178178
/// <summary>
179179
/// Raises the error.

test/Renci.SshNet.Tests/Classes/Channels/ChannelStub.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ protected override void OnClose()
6868
}
6969
}
7070

71-
protected override void OnData(byte[] data)
71+
protected override void OnData(ArraySegment<byte> data)
7272
{
7373
base.OnData(data);
7474

test/Renci.SshNet.Tests/Classes/Channels/ClientChannelStub.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ protected override void OnClose()
7272
}
7373
}
7474

75-
protected override void OnData(byte[] data)
75+
protected override void OnData(ArraySegment<byte> data)
7676
{
7777
base.OnData(data);
7878

0 commit comments

Comments
 (0)