From 3c6cd7e02321c2ef09fe4a58468cc58fb0abcb7f Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Wed, 11 May 2022 09:36:36 -0500 Subject: [PATCH] Add Stream ReadAtLeast and ReadExactly Adds methods to Stream to read at least a minimum amount of bytes, or a full buffer, of data from the stream. ReadAtLeast allows for the caller to specify whether an exception should be thrown or not on the end of the stream. Make use of the new methods where appropriate in net7.0. Fix #16598 --- .../src/System/Formats/Tar/TarHeader.Read.cs | 13 +-- .../src/System/Formats/Tar/TarHelpers.cs | 16 --- .../src/System/IO/Compression/ZipHelper.cs | 13 +-- .../Http/SocketsHttpHandler/SocksHelper.cs | 17 ++-- .../FunctionalTests/LoopbackSocksServer.cs | 11 +-- .../System/Net/Security/NegotiateStream.cs | 19 +--- .../System/Net/Security/ReadWriteAdapter.cs | 7 ++ .../src/System/Net/StreamFramer.cs | 34 +++---- .../System/Net/WebSockets/ManagedWebSocket.cs | 29 +++--- .../src/System/IO/BinaryReader.cs | 39 +------- .../src/System/IO/Stream.cs | 98 +++++++++++++++++++ .../src/System/ThrowHelper.cs | 3 + .../Json/JsonEncodingStreamWrapper.cs | 11 +-- .../src/System/Xml/EncodingStreamWrapper.cs | 9 +- .../src/System/Xml/XmlBufferReader.cs | 13 ++- .../src/System/Xml/Core/XmlReader.cs | 9 +- .../src/System/Xml/Core/XmlTextReaderImpl.cs | 8 +- .../System/Xml/Core/XmlTextReaderImplAsync.cs | 8 +- .../Internal/Utilities/StreamExtensions.cs | 4 + .../System.Runtime/ref/System.Runtime.cs | 6 ++ 20 files changed, 186 insertions(+), 181 deletions(-) diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs index f039804bffb1a..1e46719c839e9 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHeader.Read.cs @@ -26,7 +26,7 @@ internal bool TryGetNextHeader(Stream archiveStream, bool copyData) Span buffer = rented.AsSpan(0, TarHelpers.RecordSize); // minimumLength means the array could've been larger buffer.Clear(); // Rented arrays aren't clean - TarHelpers.ReadOrThrow(archiveStream, buffer); + archiveStream.ReadExactly(buffer); try { @@ -486,10 +486,7 @@ private void ReadExtendedAttributesBlock(Stream archiveStream) } byte[] buffer = new byte[(int)_size]; - if (archiveStream.Read(buffer.AsSpan()) != _size) - { - throw new EndOfStreamException(); - } + archiveStream.ReadExactly(buffer); string dataAsString = TarHelpers.GetTrimmedUtf8String(buffer); @@ -520,11 +517,7 @@ private void ReadGnuLongPathDataBlock(Stream archiveStream) } byte[] buffer = new byte[(int)_size]; - - if (archiveStream.Read(buffer.AsSpan()) != _size) - { - throw new EndOfStreamException(); - } + archiveStream.ReadExactly(buffer); string longPath = TarHelpers.GetTrimmedUtf8String(buffer); diff --git a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs index f5bf799b89558..db80de8ef0257 100644 --- a/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs +++ b/src/libraries/System.Formats.Tar/src/System/Formats/Tar/TarHelpers.cs @@ -151,22 +151,6 @@ private static string GetTrimmedString(ReadOnlySpan buffer, Encoding encod // removing the trailing null or space chars. internal static string GetTrimmedUtf8String(ReadOnlySpan buffer) => GetTrimmedString(buffer, Encoding.UTF8); - // Reads the specified number of bytes and stores it in the byte buffer passed by reference. - // Throws if end of stream is reached. - internal static void ReadOrThrow(Stream archiveStream, Span buffer) - { - int totalRead = 0; - while (totalRead < buffer.Length) - { - int bytesRead = archiveStream.Read(buffer.Slice(totalRead)); - if (bytesRead == 0) - { - throw new EndOfStreamException(); - } - totalRead += bytesRead; - } - } - // Returns true if it successfully converts the specified string to a DateTimeOffset, false otherwise. internal static bool TryConvertToDateTimeOffset(string value, out DateTimeOffset timestamp) { diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipHelper.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipHelper.cs index 1e46185461a29..7d0a887622a72 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipHelper.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZipHelper.cs @@ -40,17 +40,10 @@ internal static Encoding GetEncoding(string text) /// internal static void ReadBytes(Stream stream, byte[] buffer, int bytesToRead) { - int bytesLeftToRead = bytesToRead; - - int totalBytesRead = 0; - - while (bytesLeftToRead > 0) + int bytesRead = stream.ReadAtLeast(buffer.AsSpan(0, bytesToRead), bytesToRead, throwOnEndOfStream: false); + if (bytesRead < bytesToRead) { - int bytesRead = stream.Read(buffer, totalBytesRead, bytesLeftToRead); - if (bytesRead == 0) throw new IOException(SR.UnexpectedEndOfStream); - - totalBytesRead += bytesRead; - bytesLeftToRead -= bytesRead; + throw new IOException(SR.UnexpectedEndOfStream); } } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs index 8e7408410e2f4..d6e73370b2804 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocksHelper.cs @@ -353,18 +353,13 @@ private static ValueTask WriteAsync(Stream stream, Memory buffer, bool asy private static async ValueTask ReadToFillAsync(Stream stream, Memory buffer, bool async) { - while (buffer.Length != 0) - { - int bytesRead = async - ? await stream.ReadAsync(buffer).ConfigureAwait(false) - : stream.Read(buffer.Span); + int bytesRead = async + ? await stream.ReadAtLeastAsync(buffer, buffer.Length, throwOnEndOfStream: false).ConfigureAwait(false) + : stream.ReadAtLeast(buffer.Span, buffer.Length, throwOnEndOfStream: false); - if (bytesRead == 0) - { - throw new IOException(SR.net_http_invalid_response_premature_eof); - } - - buffer = buffer[bytesRead..]; + if (bytesRead < buffer.Length) + { + throw new IOException(SR.net_http_invalid_response_premature_eof); } } } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/LoopbackSocksServer.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/LoopbackSocksServer.cs index 781b7be21f238..465edb6ae8e58 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/LoopbackSocksServer.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/LoopbackSocksServer.cs @@ -290,16 +290,9 @@ void HandleExceptions(Exception ex) } } - private async ValueTask ReadToFillAsync(Stream stream, Memory buffer) + private ValueTask ReadToFillAsync(Stream stream, Memory buffer) { - while (!buffer.IsEmpty) - { - int bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false); - if (bytesRead == 0) - throw new Exception("Incomplete request"); - - buffer = buffer.Slice(bytesRead); - } + return stream.ReadExactlyAsync(buffer); } public async ValueTask DisposeAsync() diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs index 447cf68d89bbf..db3a21ce7b114 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/NegotiateStream.cs @@ -424,24 +424,15 @@ private async ValueTask ReadAsync(Memory buffer, Cancella static async ValueTask ReadAllAsync(Stream stream, Memory buffer, bool allowZeroRead, CancellationToken cancellationToken) { - int read = 0; - - do + int read = await TIOAdapter.ReadAtLeastAsync( + stream, buffer, buffer.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false); + if (read < buffer.Length) { - int bytes = await TIOAdapter.ReadAsync(stream, buffer, cancellationToken).ConfigureAwait(false); - if (bytes == 0) + if (read != 0 || !allowZeroRead) { - if (read != 0 || !allowZeroRead) - { - throw new IOException(SR.net_io_eof); - } - break; + throw new IOException(SR.net_io_eof); } - - buffer = buffer.Slice(bytes); - read += bytes; } - while (!buffer.IsEmpty); return read; } diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs b/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs index fd3e7059d7e62..10c689c1d12b1 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/ReadWriteAdapter.cs @@ -10,6 +10,7 @@ namespace System.Net.Security internal interface IReadWriteAdapter { static abstract ValueTask ReadAsync(Stream stream, Memory buffer, CancellationToken cancellationToken); + static abstract ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken); static abstract ValueTask WriteAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken); static abstract Task FlushAsync(Stream stream, CancellationToken cancellationToken); static abstract Task WaitAsync(TaskCompletionSource waiter); @@ -20,6 +21,9 @@ internal interface IReadWriteAdapter public static ValueTask ReadAsync(Stream stream, Memory buffer, CancellationToken cancellationToken) => stream.ReadAsync(buffer, cancellationToken); + public static ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) => + stream.ReadAtLeastAsync(buffer, minimumBytes, throwOnEndOfStream, cancellationToken); + public static ValueTask WriteAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) => stream.WriteAsync(new ReadOnlyMemory(buffer, offset, count), cancellationToken); @@ -33,6 +37,9 @@ public static ValueTask WriteAsync(Stream stream, byte[] buffer, int offset, int public static ValueTask ReadAsync(Stream stream, Memory buffer, CancellationToken cancellationToken) => new ValueTask(stream.Read(buffer.Span)); + public static ValueTask ReadAtLeastAsync(Stream stream, Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) => + new ValueTask(stream.ReadAtLeast(buffer.Span, minimumBytes, throwOnEndOfStream)); + public static ValueTask WriteAsync(Stream stream, byte[] buffer, int offset, int count, CancellationToken cancellationToken) { stream.Write(buffer, offset, count); diff --git a/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs b/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs index 9b97a29888afa..5ebf742fb5e54 100644 --- a/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs +++ b/src/libraries/System.Net.Security/src/System/Net/StreamFramer.cs @@ -31,24 +31,17 @@ internal sealed class StreamFramer byte[] buffer = _readHeaderBuffer; - int bytesRead; - int offset = 0; - while (offset < buffer.Length) + int bytesRead = await TAdapter.ReadAtLeastAsync( + stream, buffer, buffer.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false); + if (bytesRead < buffer.Length) { - bytesRead = await TAdapter.ReadAsync(stream, buffer.AsMemory(offset), cancellationToken).ConfigureAwait(false); if (bytesRead == 0) { - if (offset == 0) - { - // m_Eof, return null - _eof = true; - return null; - } - - throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed)); + // m_Eof, return null + _eof = true; + return null; } - - offset += bytesRead; + throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed)); } _curReadHeader.CopyFrom(buffer, 0); @@ -61,16 +54,11 @@ internal sealed class StreamFramer buffer = new byte[_curReadHeader.PayloadSize]; - offset = 0; - while (offset < buffer.Length) + bytesRead = await TAdapter.ReadAtLeastAsync( + stream, buffer, buffer.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false); + if (bytesRead < buffer.Length) { - bytesRead = await TAdapter.ReadAsync(stream, buffer.AsMemory(offset), cancellationToken).ConfigureAwait(false); - if (bytesRead == 0) - { - throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed)); - } - - offset += bytesRead; + throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed)); } return buffer; } diff --git a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs index 6f81d92796e55..56e9e6e726df1 100644 --- a/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs +++ b/src/libraries/System.Net.WebSockets/src/System/Net/WebSockets/ManagedWebSocket.cs @@ -779,13 +779,16 @@ private async ValueTask ReceiveAsyncPrivate(Memory paylo totalBytesReceived += receiveBufferBytesToCopy; } - while (totalBytesReceived < limit) + if (totalBytesReceived < limit) { - int numBytesRead = await _stream.ReadAsync(header.Compressed ? - _inflater!.Memory.Slice(totalBytesReceived, limit - totalBytesReceived) : - payloadBuffer.Slice(totalBytesReceived, limit - totalBytesReceived), - cancellationToken).ConfigureAwait(false); - if (numBytesRead <= 0) + int bytesToRead = limit - totalBytesReceived; + Memory readBuffer = header.Compressed ? + _inflater!.Memory.Slice(totalBytesReceived, bytesToRead) : + payloadBuffer.Slice(totalBytesReceived, bytesToRead); + + int numBytesRead = await _stream.ReadAtLeastAsync(readBuffer, bytesToRead, + throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false); + if (numBytesRead < bytesToRead) { ThrowEOFUnexpected(); break; @@ -1359,17 +1362,13 @@ private async ValueTask EnsureBufferContainsAsync(int minimumRequiredBytes, Canc _receiveBufferOffset = 0; // While we don't have enough data, read more. - while (_receiveBufferCount < minimumRequiredBytes) + int numRead = await _stream.ReadAtLeastAsync(_receiveBuffer.Slice(_receiveBufferCount), minimumRequiredBytes, + throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false); + if (numRead < minimumRequiredBytes) { - int numRead = await _stream.ReadAsync(_receiveBuffer.Slice(_receiveBufferCount), cancellationToken).ConfigureAwait(false); - Debug.Assert(numRead >= 0, $"Expected non-negative bytes read, got {numRead}"); - if (numRead <= 0) - { - ThrowEOFUnexpected(); - break; - } - _receiveBufferCount += numRead; + ThrowEOFUnexpected(); } + _receiveBufferCount += numRead; } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs b/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs index 3d157427060b5..32b0c63a2f55c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/BinaryReader.cs @@ -483,18 +483,7 @@ public virtual byte[] ReadBytes(int count) } byte[] result = new byte[count]; - int numRead = 0; - do - { - int n = _stream.Read(result, numRead, count); - if (n == 0) - { - break; - } - - numRead += n; - count -= n; - } while (count > 0); + int numRead = _stream.ReadAtLeast(result, result.Length, throwOnEndOfStream: false); if (numRead != result.Length) { @@ -521,16 +510,7 @@ private ReadOnlySpan InternalRead(int numBytes) { ThrowIfDisposed(); - int bytesRead = 0; - do - { - int n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead); - if (n == 0) - { - ThrowHelper.ThrowEndOfFileException(); - } - bytesRead += n; - } while (bytesRead < numBytes); + _stream.ReadExactly(_buffer.AsSpan(0, numBytes)); return _buffer; } @@ -547,9 +527,6 @@ protected virtual void FillBuffer(int numBytes) throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_BinaryReaderFillBuffer); } - int bytesRead = 0; - int n; - ThrowIfDisposed(); // Need to find a good threshold for calling ReadByte() repeatedly @@ -557,7 +534,7 @@ protected virtual void FillBuffer(int numBytes) // streams. if (numBytes == 1) { - n = _stream.ReadByte(); + int n = _stream.ReadByte(); if (n == -1) { ThrowHelper.ThrowEndOfFileException(); @@ -567,15 +544,7 @@ protected virtual void FillBuffer(int numBytes) return; } - do - { - n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead); - if (n == 0) - { - ThrowHelper.ThrowEndOfFileException(); - } - bytesRead += n; - } while (bytesRead < numBytes); + _stream.ReadExactly(_buffer.AsSpan(0, numBytes)); } public int Read7BitEncodedInt() diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs index 2c823b41c0df0..fa9940a145dff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs @@ -332,6 +332,48 @@ static async ValueTask FinishReadAsync(Task readTask, byte[] localBuff } } + public async ValueTask ReadExactlyAsync(Memory buffer, CancellationToken cancellationToken = default) => + _ = await ReadAtLeastAsyncCore(buffer, buffer.Length, throwOnEndOfStream: true, cancellationToken).ConfigureAwait(false); + + public async ValueTask ReadExactlyAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default) + { + ValidateBufferArguments(buffer, offset, count); + + _ = await ReadAtLeastAsyncCore(buffer.AsMemory(offset, count), count, throwOnEndOfStream: true, cancellationToken).ConfigureAwait(false); + } + + public ValueTask ReadAtLeastAsync(Memory buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default) + { + ValidateReadAtLeastArguments(buffer.Length, minimumBytes); + + return ReadAtLeastAsyncCore(buffer, minimumBytes, throwOnEndOfStream: true, cancellationToken); + } + + // No argument checking is done here. It is up to the caller. + private async ValueTask ReadAtLeastAsyncCore(Memory buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) + { + int totalRead = 0; + do + { + int read = await ReadAsync(buffer.Slice(totalRead), cancellationToken).ConfigureAwait(false); + if (read == 0) + { + if (throwOnEndOfStream) + { + ThrowHelper.ThrowEndOfFileException(); + } + else + { + return totalRead; + } + } + + totalRead += read; + } while (totalRead < minimumBytes); + + return totalRead; + } + #if NATIVEAOT // TODO: https://github.com/dotnet/corert/issues/3251 private static bool HasOverriddenBeginEndRead() => true; @@ -701,6 +743,48 @@ public virtual int ReadByte() return r == 0 ? -1 : oneByteArray[0]; } + public void ReadExactly(Span buffer) => + _ = ReadAtLeastCore(buffer, buffer.Length, throwOnEndOfStream: true); + + public void ReadExactly(byte[] buffer, int offset, int count) + { + ValidateBufferArguments(buffer, offset, count); + + _ = ReadAtLeastCore(buffer.AsSpan(offset, count), count, throwOnEndOfStream: true); + } + + public int ReadAtLeast(Span buffer, int minimumBytes, bool throwOnEndOfStream = true) + { + ValidateReadAtLeastArguments(buffer.Length, minimumBytes); + + return ReadAtLeastCore(buffer, minimumBytes, throwOnEndOfStream); + } + + // No argument checking is done here. It is up to the caller. + private int ReadAtLeastCore(Span buffer, int minimumBytes, bool throwOnEndOfStream) + { + int totalRead = 0; + do + { + int read = Read(buffer.Slice(totalRead)); + if (read == 0) + { + if (throwOnEndOfStream) + { + ThrowHelper.ThrowEndOfFileException(); + } + else + { + return totalRead; + } + } + + totalRead += read; + } while (totalRead < minimumBytes); + + return totalRead; + } + public abstract void Write(byte[] buffer, int offset, int count); public virtual void Write(ReadOnlySpan buffer) @@ -757,6 +841,20 @@ protected static void ValidateBufferArguments(byte[] buffer, int offset, int cou } } + private static void ValidateReadAtLeastArguments(int bufferLength, int minimumBytes) + { + if (minimumBytes < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.minimumBytes, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (bufferLength < minimumBytes) + { + // TODO: pick the right exception message here + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.minimumBytes); + } + } + /// Validates arguments provided to the or methods. /// The "destination" argument passed to the copy method. /// The integer "bufferSize" argument passed to the copy method. diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index f85a1214e7d22..42136204f3159 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -888,6 +888,8 @@ private static string GetArgumentName(ExceptionArgument argument) return "anyOf"; case ExceptionArgument.overlapped: return "overlapped"; + case ExceptionArgument.minimumBytes: + return "minimumBytes"; default: Debug.Fail("The enum value is not defined, please check the ExceptionArgument Enum."); return ""; @@ -1160,6 +1162,7 @@ internal enum ExceptionArgument stream, anyOf, overlapped, + minimumBytes, } // diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonEncodingStreamWrapper.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonEncodingStreamWrapper.cs index 5b6c17ac94779..8f30f153be140 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonEncodingStreamWrapper.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Runtime/Serialization/Json/JsonEncodingStreamWrapper.cs @@ -419,16 +419,9 @@ private void FillBuffer(int count) Debug.Assert(_bytes != null); count -= _byteCount; - while (count > 0) + if (count > 0) { - int read = _stream.Read(_bytes, _byteOffset + _byteCount, count); - if (read == 0) - { - break; - } - - _byteCount += read; - count -= read; + _byteCount += _stream.ReadAtLeast(_bytes.AsSpan(_byteOffset + _byteCount, count), count, throwOnEndOfStream: false); } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/EncodingStreamWrapper.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/EncodingStreamWrapper.cs index d6de2307fb363..e0fe24ba58163 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/EncodingStreamWrapper.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/EncodingStreamWrapper.cs @@ -281,14 +281,9 @@ private static SupportedEncoding ReadBOMEncoding(byte b1, byte b2, byte b3, byte private void FillBuffer(int count) { count -= _byteCount; - while (count > 0) + if (count > 0) { - int read = _stream.Read(_bytes!, _byteOffset + _byteCount, count); - if (read == 0) - break; - - _byteCount += read; - count -= read; + _byteCount += _stream.ReadAtLeast(_bytes.AsSpan(_byteOffset + _byteCount, count), count, throwOnEndOfStream: false); } } diff --git a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs index db06d82ade988..b72baabf818ac 100644 --- a/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs +++ b/src/libraries/System.Private.DataContractSerialization/src/System/Xml/XmlBufferReader.cs @@ -234,14 +234,13 @@ private bool TryEnsureBytes(int count) } int needed = newOffsetMax - _offsetMax; DiagnosticUtility.DebugAssert(needed > 0, ""); - do + int read = _stream.ReadAtLeast(_buffer.AsSpan(_offsetMax, needed), needed, throwOnEndOfStream: false); + _offsetMax += read; + + if (read < needed) { - int actual = _stream.Read(_buffer, _offsetMax, needed); - if (actual == 0) - return false; - _offsetMax += actual; - needed -= actual; - } while (needed > 0); + return false; + } } while (true); } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlReader.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlReader.cs index 9efea7bfb27c8..3ed40219d064c 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlReader.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlReader.cs @@ -1714,13 +1714,8 @@ internal static XmlReader CreateSqlReader(Stream input, XmlReaderSettings? setti // allocate byte buffer byte[] bytes = new byte[CalcBufferSize(input)]; - int byteCount = 0; - int read; - do - { - read = input.Read(bytes, byteCount, bytes.Length - byteCount); - byteCount += read; - } while (read > 0 && byteCount < 2); + int bytesToRead = Math.Min(bytes.Length, 2); + int byteCount = input.ReadAtLeast(bytes, bytesToRead, throwOnEndOfStream: false); // create text or binary XML reader depending on the stream first 2 bytes if (byteCount >= 2 && bytes[0] == 0xdf && bytes[1] == 0xff) diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs index 5173cc001bb1e..cb568a71fd35a 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImpl.cs @@ -2950,13 +2950,13 @@ private void InitStreamInput(Uri? baseUri, string baseUriStr, Stream stream, byt // make sure we have at least 4 bytes to detect the encoding (no preamble of System.Text supported encoding is longer than 4 bytes) _ps.bytePos = 0; - while (_ps.bytesUsed < 4 && _ps.bytes.Length - _ps.bytesUsed > 0) + const int bytesToRead = 4; + if (_ps.bytesUsed < bytesToRead && _ps.bytes.Length - _ps.bytesUsed > 0) { - int read = stream.Read(_ps.bytes, _ps.bytesUsed, _ps.bytes.Length - _ps.bytesUsed); - if (read == 0) + int read = stream.ReadAtLeast(_ps.bytes.AsSpan(_ps.bytesUsed), bytesToRead, throwOnEndOfStream: false); + if (read < bytesToRead) { _ps.isStreamEof = true; - break; } _ps.bytesUsed += read; } diff --git a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImplAsync.cs b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImplAsync.cs index 6257fa4678d73..74aad41b91628 100644 --- a/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImplAsync.cs +++ b/src/libraries/System.Private.Xml/src/System/Xml/Core/XmlTextReaderImplAsync.cs @@ -953,13 +953,13 @@ private async Task InitStreamInputAsync(Uri? baseUri, string baseUriStr, Stream // make sure we have at least 4 bytes to detect the encoding (no preamble of System.Text supported encoding is longer than 4 bytes) _ps.bytePos = 0; - while (_ps.bytesUsed < 4 && _ps.bytes.Length - _ps.bytesUsed > 0) + const int bytesToRead = 4; + if (_ps.bytesUsed < bytesToRead && _ps.bytes.Length - _ps.bytesUsed > 0) { - int read = await stream.ReadAsync(_ps.bytes.AsMemory(_ps.bytesUsed)).ConfigureAwait(false); - if (read == 0) + int read = await stream.ReadAtLeastAsync(_ps.bytes.AsMemory(_ps.bytesUsed), bytesToRead, throwOnEndOfStream: false).ConfigureAwait(false); + if (read < bytesToRead) { _ps.isStreamEof = true; - break; } _ps.bytesUsed += read; } diff --git a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs index e3fd02eee4b25..bb52925254c27 100644 --- a/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs +++ b/src/libraries/System.Reflection.Metadata/src/System/Reflection/Internal/Utilities/StreamExtensions.cs @@ -78,6 +78,9 @@ internal static int TryReadAll(this Stream stream, byte[] buffer, int offset, in #if NETCOREAPP internal static int TryReadAll(this Stream stream, Span buffer) +#if NET7_0_OR_GREATER + => stream.ReadAtLeast(buffer, buffer.Length, throwOnEndOfStream: false); +#else { int totalBytesRead = 0; while (totalBytesRead < buffer.Length) @@ -93,6 +96,7 @@ internal static int TryReadAll(this Stream stream, Span buffer) return totalBytesRead; } +#endif #endif /// diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index feb44b077a597..cc9df5bac50ac 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -9169,7 +9169,13 @@ protected virtual void ObjectInvariant() { } public System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count) { throw null; } public virtual System.Threading.Tasks.Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; } public virtual System.Threading.Tasks.ValueTask ReadAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public int ReadAtLeast(System.Span buffer, int minimumBytes, bool throwOnEndOfStream = true) { throw null; } + public System.Threading.Tasks.ValueTask ReadAtLeastAsync(System.Memory buffer, int minimumBytes, bool throwOnEndOfStream = true, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public virtual int ReadByte() { throw null; } + public void ReadExactly(byte[] buffer, int offset, int count) { } + public void ReadExactly(System.Span buffer) { } + public System.Threading.Tasks.ValueTask ReadExactlyAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + public System.Threading.Tasks.ValueTask ReadExactlyAsync(System.Memory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public abstract long Seek(long offset, System.IO.SeekOrigin origin); public abstract void SetLength(long value); public static System.IO.Stream Synchronized(System.IO.Stream stream) { throw null; }