11using System ;
2+ using System . Buffers . Binary ;
23using System . Collections . Generic ;
34using System . Diagnostics ;
45using System . Globalization ;
@@ -22,8 +23,8 @@ internal sealed class SftpSession : SubsystemSession, ISftpSession
2223
2324 private readonly Dictionary < uint , SftpRequest > _requests = new Dictionary < uint , SftpRequest > ( ) ;
2425 private readonly ISftpResponseFactory _sftpResponseFactory ;
25- private readonly List < byte > _data = new List < byte > ( 32 * 1024 ) ;
2626 private readonly Encoding _encoding ;
27+ private System . Net . ArrayBuffer _buffer = new ( 32 * 1024 ) ;
2728 private EventWaitHandle _sftpVersionConfirmed = new AutoResetEvent ( initialState : false ) ;
2829 private IDictionary < string , string > _supportedExtensions ;
2930
@@ -303,125 +304,77 @@ protected override void OnChannelOpen()
303304
304305 protected override void OnDataReceived( byte [ ] data )
305306 {
306- const int packetLengthByteCount = 4 ;
307- const int sftpMessageTypeByteCount = 1 ;
308- const int minimumChannelDataLength = packetLengthByteCount + sftpMessageTypeByteCount ;
307+ ArraySegment< byte > d = new ( data ) ;
309308
310- var offset = 0 ;
311- var count = data. Length ;
312-
313- // improve performance and reduce GC pressure by not buffering channel data if the received
314- // chunk contains the complete packet data.
315- //
316- // for this, the buffer should be empty and the chunk should contain at least the packet length
317- // and the type of the SFTP message
318- if ( _data . Count == 0 )
309+ // If the buffer is empty then skip a copy and read packets
310+ // directly out of the given data.
311+ if ( _buffer . ActiveLength == 0 )
319312 {
320- while ( count >= minimumChannelDataLength )
313+ while ( d . Count >= 4 )
321314 {
322- // extract packet length
323- var packetDataLength = data[ offset ] << 24 | data [ offset + 1 ] << 16 | data [ offset + 2 ] << 8 |
324- data [ offset + 3 ] ;
325-
326- var packetTotalLength = packetDataLength + packetLengthByteCount ;
315+ var packetLength = BinaryPrimitives. ReadInt32BigEndian ( d ) ;
327316
328- // check if complete packet data (or more) is available
329- if ( count >= packetTotalLength )
317+ if ( d . Count - 4 < packetLength )
330318 {
331- // load and process SFTP message
332- if ( ! TryLoadSftpMessage ( data , offset + packetLengthByteCount , packetDataLength ) )
333- {
334- return ;
335- }
336-
337- // remove processed bytes from the number of bytes to process as the channel
338- // data we received may contain (part of) another message
339- count -= packetTotalLength ;
340-
341- // move offset beyond bytes we just processed
342- offset += packetTotalLength ;
319+ break ;
343320 }
344- else
321+
322+ if ( ! TryLoadSftpMessage ( d . Slice ( 4 , packetLength ) ) )
345323 {
346- // we don't have a complete message
347- break ;
324+ // An error occured.
325+ return ;
348326 }
349- }
350327
351- // check if there is channel data left to process or buffer
352- if ( count == 0 )
353- {
354- return ;
328+ d = d. Slice ( 4 + packetLength ) ;
355329 }
356330
357- // check if we processed part of the channel data we received
358- if ( offset > 0 )
359- {
360- // add (remaining) channel data to internal data holder
361- var remainingChannelData = new byte [ count ] ;
362- Buffer. BlockCopy ( data , offset , remainingChannelData , 0 , count ) ;
363- _data. AddRange ( remainingChannelData ) ;
364- }
365- else
331+ if ( d . Count > 0 )
366332 {
367- // add (remaining) channel data to internal data holder
368- _data. AddRange ( data ) ;
333+ // Now buffer the remainder.
334+ _buffer. EnsureAvailableSpace ( d . Count ) ;
335+ d. AsSpan ( ) . CopyTo ( _buffer . AvailableSpan ) ;
336+ _buffer. Commit ( d . Count ) ;
369337 }
370338
371- // skip further processing as we'll need a new chunk to complete the message
372339 return ;
373340 }
374341
375- // add (remaining) channel data to internal data holder
376- _data. AddRange ( data ) ;
342+ // The buffer already had some data. Append the new data and
343+ // proceed with reading out packets.
344+ _buffer. EnsureAvailableSpace ( d . Count ) ;
345+ d. AsSpan ( ) . CopyTo ( _buffer . AvailableSpan ) ;
346+ _buffer. Commit ( d . Count ) ;
377347
378- while ( _data . Count >= minimumChannelDataLength )
348+ while ( _buffer . ActiveLength >= 4 )
379349 {
380- // extract packet length
381- var packetDataLength = _data[ 0 ] << 24 | _data [ 1 ] << 16 | _data [ 2 ] << 8 | _data [ 3 ] ;
350+ d = new ArraySegment< byte > (
351+ _buffer . DangerousGetUnderlyingBuffer ( ) ,
352+ _buffer . ActiveStartOffset ,
353+ _buffer . ActiveLength ) ;
382354
383- var packetTotalLength = packetDataLength + packetLengthByteCount ;
355+ var packetLength = BinaryPrimitives . ReadInt32BigEndian ( d ) ;
384356
385- // check if complete packet data is available
386- if ( _data . Count < packetTotalLength )
357+ if ( d . Count - 4 < packetLength )
387358 {
388- // wait for complete message to arrive first
389359 break ;
390360 }
391361
392- // create buffer to hold packet data
393- var packetData = new byte [ packetDataLength ] ;
362+ // Note: the packet data in the buffer is safe to read from
363+ // only for the duration of this load. If it needs to be stored,
364+ // callees should make their own copy.
365+ _ = TryLoadSftpMessage ( d . Slice ( 4 , packetLength ) ) ;
394366
395- // copy packet data and bytes for length to array
396- _data . CopyTo ( packetLengthByteCount , packetData , 0 , packetDataLength ) ;
397-
398- // remove loaded data and bytes for length from _data holder
399- if ( _data . Count == packetTotalLength )
400- {
401- // the only buffered data is the data we're processing
402- _data. Clear ( ) ;
403- }
404- else
405- {
406- // remove only the data we're processing
407- _data. RemoveRange ( 0 , packetTotalLength ) ;
408- }
409-
410- // load and process SFTP message
411- if ( ! TryLoadSftpMessage ( packetData , 0 , packetDataLength ) )
412- {
413- break ;
414- }
367+ _buffer. Discard ( 4 + packetLength ) ;
415368 }
416369 }
417370
418- private bool TryLoadSftpMessage( byte [ ] packetData , int offset , int count )
371+ private bool TryLoadSftpMessage( ArraySegment < byte > packetData )
419372 {
420373 // Create SFTP message
421- var response = _sftpResponseFactory. Create ( ProtocolVersion , packetData [ offset ] , _encoding ) ;
374+ var response = _sftpResponseFactory . Create ( ProtocolVersion , packetData . Array [ packetData . Offset ] , _encoding ) ;
422375
423376 // Load message data into it
424- response. Load ( packetData , offset + 1 , count - 1 ) ;
377+ response . Load ( packetData . Array , packetData . Offset + 1 , packetData . Count - 1 ) ;
425378
426379 try
427380 {
0 commit comments