@@ -27,6 +27,8 @@ public partial class SslStream
27
27
private const int HandshakeTypeOffsetSsl2 = 2 ; // Offset of HelloType in Sslv2 and Unified frames
28
28
private const int HandshakeTypeOffsetTls = 5 ; // Offset of HelloType in Sslv3 and TLS frames
29
29
30
+ private const int UnknownTlsFrameLength = int . MaxValue ; // frame too short to determine length
31
+
30
32
private bool _receivedEOF ;
31
33
32
34
// Used by Telemetry to ensure we log connection close exactly once
@@ -211,12 +213,10 @@ private async Task RenegotiateAsync<TIOAdapter>(CancellationToken cancellationTo
211
213
throw SslStreamPal . GetException ( status ) ;
212
214
}
213
215
214
- _buffer . EnsureAvailableSpace ( InitialHandshakeBufferSize ) ;
215
-
216
216
ProtocolToken message ;
217
217
do
218
218
{
219
- int frameSize = await ReceiveTlsFrameAsync < TIOAdapter > ( cancellationToken ) . ConfigureAwait ( false ) ;
219
+ int frameSize = await ReceiveHandshakeFrameAsync < TIOAdapter > ( cancellationToken ) . ConfigureAwait ( false ) ;
220
220
ProcessTlsFrame ( frameSize , out message ) ;
221
221
222
222
if ( message . Size > 0 )
@@ -291,7 +291,7 @@ private async Task ForceAuthenticationAsync<TIOAdapter>(bool receiveFirst, byte[
291
291
292
292
while ( ! handshakeCompleted )
293
293
{
294
- int frameSize = await ReceiveTlsFrameAsync < TIOAdapter > ( cancellationToken ) . ConfigureAwait ( false ) ;
294
+ int frameSize = await ReceiveHandshakeFrameAsync < TIOAdapter > ( cancellationToken ) . ConfigureAwait ( false ) ;
295
295
ProcessTlsFrame ( frameSize , out message ) ;
296
296
297
297
ReadOnlyMemory < byte > payload = default ;
@@ -359,10 +359,10 @@ private async Task ForceAuthenticationAsync<TIOAdapter>(bool receiveFirst, byte[
359
359
}
360
360
361
361
// This method will make sure we have at least one full TLS frame buffered.
362
- private async ValueTask < int > ReceiveTlsFrameAsync < TIOAdapter > ( CancellationToken cancellationToken )
362
+ private async ValueTask < int > ReceiveHandshakeFrameAsync < TIOAdapter > ( CancellationToken cancellationToken )
363
363
where TIOAdapter : IReadWriteAdapter
364
364
{
365
- int frameSize = await EnsureFullTlsFrameAsync < TIOAdapter > ( cancellationToken ) . ConfigureAwait ( false ) ;
365
+ int frameSize = await EnsureFullTlsFrameAsync < TIOAdapter > ( cancellationToken , InitialHandshakeBufferSize ) . ConfigureAwait ( false ) ;
366
366
367
367
if ( frameSize == 0 )
368
368
{
@@ -699,38 +699,27 @@ private void ReturnReadBufferIfEmpty()
699
699
700
700
private bool HaveFullTlsFrame ( out int frameSize )
701
701
{
702
- if ( _buffer . EncryptedLength < TlsFrameHelper . HeaderSize )
703
- {
704
- frameSize = int . MaxValue ;
705
- return false ;
706
- }
707
-
708
702
frameSize = GetFrameSize ( _buffer . EncryptedReadOnlySpan ) ;
709
703
return _buffer . EncryptedLength >= frameSize ;
710
704
}
711
705
712
706
[ AsyncMethodBuilder ( typeof ( PoolingAsyncValueTaskMethodBuilder < > ) ) ]
713
- private async ValueTask < int > EnsureFullTlsFrameAsync < TIOAdapter > ( CancellationToken cancellationToken )
707
+ private async ValueTask < int > EnsureFullTlsFrameAsync < TIOAdapter > ( CancellationToken cancellationToken , int estimatedSize )
714
708
where TIOAdapter : IReadWriteAdapter
715
709
{
716
- int frameSize ;
717
- if ( HaveFullTlsFrame ( out frameSize ) )
710
+ if ( HaveFullTlsFrame ( out int frameSize ) )
718
711
{
719
712
return frameSize ;
720
713
}
721
714
722
- if ( frameSize != int . MaxValue )
723
- {
724
- // make sure we have space for the whole frame
725
- _buffer . EnsureAvailableSpace ( frameSize - _buffer . EncryptedLength ) ;
726
- }
727
- else
728
- {
729
- // move existing data to the beginning of the buffer (they will
730
- // be couple of bytes only, otherwise we would have entire
731
- // header and know exact size)
732
- _buffer . EnsureAvailableSpace ( _buffer . Capacity - _buffer . EncryptedLength ) ;
733
- }
715
+ await TIOAdapter . ReadAsync ( InnerStream , Memory < byte > . Empty , cancellationToken ) . ConfigureAwait ( false ) ;
716
+
717
+ // If we don't have enough data to determine the frame size, use the provided estimate
718
+ // (e.g. a full TLS frame for reads, and a somewhat shorter frame for handshake / renegotiation).
719
+ // If we do know the frame size, ensure we have space for the whole frame.
720
+ _buffer . EnsureAvailableSpace ( frameSize == UnknownTlsFrameLength ?
721
+ estimatedSize :
722
+ frameSize - _buffer . EncryptedLength ) ;
734
723
735
724
while ( _buffer . EncryptedLength < frameSize )
736
725
{
@@ -806,6 +795,7 @@ private SecurityStatusPal DecryptData(int frameSize)
806
795
private async ValueTask < int > ReadAsyncInternal < TIOAdapter > ( Memory < byte > buffer , CancellationToken cancellationToken )
807
796
where TIOAdapter : IReadWriteAdapter
808
797
{
798
+
809
799
// Throw first if we already have exception.
810
800
// Check for disposal is not atomic so we will check again below.
811
801
ThrowIfExceptionalOrNotAuthenticated ( ) ;
@@ -819,11 +809,12 @@ private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(Memory<byte> buffer,
819
809
try
820
810
{
821
811
int processedLength = 0 ;
812
+ int nextTlsFrameLength = UnknownTlsFrameLength ;
822
813
823
814
if ( _buffer . DecryptedLength != 0 )
824
815
{
825
816
processedLength = CopyDecryptedData ( buffer ) ;
826
- if ( processedLength == buffer . Length || ! HaveFullTlsFrame ( out _ ) )
817
+ if ( processedLength == buffer . Length || ! HaveFullTlsFrame ( out nextTlsFrameLength ) )
827
818
{
828
819
// We either filled whole buffer or used all buffered frames.
829
820
return processedLength ;
@@ -832,32 +823,19 @@ private async ValueTask<int> ReadAsyncInternal<TIOAdapter>(Memory<byte> buffer,
832
823
buffer = buffer . Slice ( processedLength ) ;
833
824
}
834
825
835
- if ( _receivedEOF )
826
+ if ( _receivedEOF && nextTlsFrameLength == UnknownTlsFrameLength )
836
827
{
828
+ // there should be no frames waiting for processing
837
829
Debug . Assert ( _buffer . EncryptedLength == 0 ) ;
838
830
// We received EOF during previous read but had buffered data to return.
839
831
return 0 ;
840
832
}
841
833
842
- if ( buffer . Length == 0 && _buffer . ActiveLength == 0 )
843
- {
844
- // User requested a zero-byte read, and we have no data available in the buffer for processing.
845
- // This zero-byte read indicates their desire to trade off the extra cost of a zero-byte read
846
- // for reduced memory consumption when data is not immediately available.
847
- // So, we will issue our own zero-byte read against the underlying stream and defer buffer allocation
848
- // until data is actually available from the underlying stream.
849
- // Note that if the underlying stream does not supporting blocking on zero byte reads, then this will
850
- // complete immediately and won't save any memory, but will still function correctly.
851
- await TIOAdapter . ReadAsync ( InnerStream , Memory < byte > . Empty , cancellationToken ) . ConfigureAwait ( false ) ;
852
- }
853
-
854
834
Debug . Assert ( _buffer . DecryptedLength == 0 ) ;
855
835
856
- _buffer . EnsureAvailableSpace ( ReadBufferSize - _buffer . ActiveLength ) ;
857
-
858
836
while ( true )
859
837
{
860
- int payloadBytes = await EnsureFullTlsFrameAsync < TIOAdapter > ( cancellationToken ) . ConfigureAwait ( false ) ;
838
+ int payloadBytes = await EnsureFullTlsFrameAsync < TIOAdapter > ( cancellationToken , ReadBufferSize ) . ConfigureAwait ( false ) ;
861
839
if ( payloadBytes == 0 )
862
840
{
863
841
_receivedEOF = true ;
@@ -1009,6 +987,11 @@ private int CopyDecryptedData(Memory<byte> buffer)
1009
987
// Returns TLS Frame size including header size.
1010
988
private int GetFrameSize ( ReadOnlySpan < byte > buffer )
1011
989
{
990
+ if ( buffer . Length < TlsFrameHelper . HeaderSize )
991
+ {
992
+ return UnknownTlsFrameLength ;
993
+ }
994
+
1012
995
if ( ! TlsFrameHelper . TryGetFrameHeader ( buffer , ref _lastFrame . Header ) )
1013
996
{
1014
997
throw new IOException ( SR . net_ssl_io_frame ) ;
0 commit comments