Span<byte> 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
{
}
byte[] buffer = new byte[(int)_size];
- if (archiveStream.Read(buffer.AsSpan()) != _size)
- {
- throw new EndOfStreamException();
- }
+ archiveStream.ReadExactly(buffer);
string dataAsString = TarHelpers.GetTrimmedUtf8String(buffer);
}
byte[] buffer = new byte[(int)_size];
-
- if (archiveStream.Read(buffer.AsSpan()) != _size)
- {
- throw new EndOfStreamException();
- }
+ archiveStream.ReadExactly(buffer);
string longPath = TarHelpers.GetTrimmedUtf8String(buffer);
// removing the trailing null or space chars.
internal static string GetTrimmedUtf8String(ReadOnlySpan<byte> 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<byte> 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)
{
/// </summary>
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);
}
}
Assert.Throws<ObjectDisposedException>(() => binaryReader.Read(new Span<char>()));
}
}
+
+ private class DerivedBinaryReader : BinaryReader
+ {
+ public DerivedBinaryReader(Stream input) : base(input) { }
+
+ public void CallFillBuffer0()
+ {
+ FillBuffer(0);
+ }
+ }
+
+ [Fact]
+ public void FillBuffer_Zero_Throws()
+ {
+ using Stream stream = CreateStream();
+
+ string hello = "Hello";
+ stream.Write(Encoding.ASCII.GetBytes(hello));
+ stream.Position = 0;
+
+ using var derivedReader = new DerivedBinaryReader(stream);
+ Assert.Throws<EndOfStreamException>(derivedReader.CallFillBuffer0);
+ }
}
}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ public class Stream_ReadAtLeast
+ {
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task DelegatesToRead_Success(bool async)
+ {
+ bool readInvoked = false;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvoked = true;
+ Assert.NotNull(array);
+ Assert.Equal(0, offset);
+ Assert.Equal(30, count);
+
+ for (int i = 0; i < 10; i++) array[offset + i] = (byte)i;
+ return 10;
+ });
+
+ byte[] buffer = new byte[30];
+
+ Assert.Equal(10, async ? await s.ReadAtLeastAsync(buffer, 10) : s.ReadAtLeast(buffer, 10));
+ Assert.True(readInvoked);
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 30; i++) Assert.Equal(0, buffer[i]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadMoreThanOnePage(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ for (int i = 0; i < 10; i++) array[offset + i] = (byte)i;
+ return 10;
+ });
+
+ byte[] buffer = new byte[30];
+
+ Assert.Equal(20, async ? await s.ReadAtLeastAsync(buffer, 20) : s.ReadAtLeast(buffer, 20));
+ Assert.Equal(2, readInvokedCount);
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 20; i++) Assert.Equal(i - 10, buffer[i]);
+ for (int i = 20; i < 30; i++) Assert.Equal(0, buffer[i]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadMoreThanMinimumBytes(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ // first try with a buffer that doesn't fill 3 full pages
+ byte[] buffer = new byte[28];
+
+ Assert.Equal(28, async ? await s.ReadAtLeastAsync(buffer, 22) : s.ReadAtLeast(buffer, 22));
+ Assert.Equal(3, readInvokedCount);
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 20; i++) Assert.Equal(i - 10, buffer[i]);
+ for (int i = 20; i < 28; i++) Assert.Equal(i - 20, buffer[i]);
+
+ // now try with a buffer that is bigger than 3 pages
+ readInvokedCount = 0;
+ buffer = new byte[32];
+
+ Assert.Equal(30, async ? await s.ReadAtLeastAsync(buffer, 22) : s.ReadAtLeast(buffer, 22));
+ Assert.Equal(3, readInvokedCount);
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 20; i++) Assert.Equal(i - 10, buffer[i]);
+ for (int i = 20; i < 30; i++) Assert.Equal(i - 20, buffer[i]);
+ for (int i = 30; i < 32; i++) Assert.Equal(0, buffer[i]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadAtLeastZero(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[20];
+
+ // ReadAtLeast minimumBytes=0 is a no-op
+ Assert.Equal(0, async ? await s.ReadAtLeastAsync(buffer, 0) : s.ReadAtLeast(buffer, 0));
+ Assert.Equal(0, readInvokedCount);
+
+ // now try with an empty buffer
+ byte[] emptyBuffer = Array.Empty<byte>();
+
+ Assert.Equal(0, async ? await s.ReadAtLeastAsync(emptyBuffer, 0) : s.ReadAtLeast(emptyBuffer, 0));
+ Assert.Equal(0, readInvokedCount);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task NegativeMinimumBytes(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[10];
+ if (async)
+ {
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadAtLeastAsync(buffer, -1));
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadAtLeastAsync(buffer, -10));
+ }
+ else
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadAtLeast(buffer, -1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadAtLeast(buffer, -10));
+ }
+ Assert.Equal(0, readInvokedCount);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task BufferSmallerThanMinimumBytes(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[20];
+ byte[] emptyBuffer = Array.Empty<byte>();
+ if (async)
+ {
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadAtLeastAsync(buffer, 21));
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadAtLeastAsync(emptyBuffer, 1));
+ }
+ else
+ {
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadAtLeast(buffer, 21));
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadAtLeast(emptyBuffer, 1));
+ }
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task HandleEndOfStream(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ if (readInvokedCount == 1)
+ {
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ }
+ else
+ {
+ return 0;
+ }
+ });
+
+ byte[] buffer = new byte[20];
+ if (async)
+ {
+ await Assert.ThrowsAsync<EndOfStreamException>(async () => await s.ReadAtLeastAsync(buffer, 11));
+ }
+ else
+ {
+ Assert.Throws<EndOfStreamException>(() => s.ReadAtLeast(buffer, 11));
+ }
+ Assert.Equal(2, readInvokedCount);
+
+ readInvokedCount = 0;
+
+ Assert.Equal(10, async ? await s.ReadAtLeastAsync(buffer, 11, throwOnEndOfStream: false) : s.ReadAtLeast(buffer, 11, throwOnEndOfStream: false));
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 20; i++) Assert.Equal(0, buffer[i]);
+ Assert.Equal(2, readInvokedCount);
+ }
+
+ [Fact]
+ public async Task CancellationTokenIsPassedThrough()
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readAsyncFunc: (array, offset, count, cancellationToken) =>
+ {
+ readInvokedCount++;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return Task.FromResult(10);
+ });
+
+ byte[] buffer = new byte[20];
+
+ using CancellationTokenSource cts = new CancellationTokenSource();
+ CancellationToken token = cts.Token;
+ cts.Cancel();
+
+ await Assert.ThrowsAsync<OperationCanceledException>(async () => await s.ReadAtLeastAsync(buffer, 10, cancellationToken: token));
+ Assert.Equal(1, readInvokedCount);
+ }
+ }
+}
--- /dev/null
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace System.IO.Tests
+{
+ public class Stream_ReadExactly
+ {
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task DelegatesToRead_Success(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[30];
+
+ if (async)
+ {
+ await s.ReadExactlyAsync(buffer);
+ }
+ else
+ {
+ s.ReadExactly(buffer);
+ }
+
+ Assert.Equal(3, readInvokedCount);
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 20; i++) Assert.Equal(i - 10, buffer[i]);
+ for (int i = 20; i < 30; i++) Assert.Equal(i - 20, buffer[i]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task DelegatesToRead_Success_OffsetCount(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[30];
+
+ if (async)
+ {
+ await s.ReadExactlyAsync(buffer, 0, 10);
+ }
+ else
+ {
+ s.ReadExactly(buffer, 0, 10);
+ }
+
+ Assert.Equal(1, readInvokedCount);
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 30; i++) Assert.Equal(0, buffer[i]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadPartialPageCorrectly(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[25];
+
+ if (async)
+ {
+ await s.ReadExactlyAsync(buffer);
+ }
+ else
+ {
+ s.ReadExactly(buffer);
+ }
+
+ Assert.Equal(3, readInvokedCount);
+ for (int i = 0; i < 10; i++) Assert.Equal(i, buffer[i]);
+ for (int i = 10; i < 20; i++) Assert.Equal(i - 10, buffer[i]);
+ for (int i = 20; i < 25; i++) Assert.Equal(i - 20, buffer[i]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadPartialPageCorrectly_OffsetCount(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[25];
+
+ if (async)
+ {
+ await s.ReadExactlyAsync(buffer, 5, 15);
+ }
+ else
+ {
+ s.ReadExactly(buffer, 5, 15);
+ }
+
+ Assert.Equal(2, readInvokedCount);
+ for (int i = 0; i < 5; i++) Assert.Equal(0, buffer[i]);
+ for (int i = 5; i < 15; i++) Assert.Equal(i - 5, buffer[i]);
+ for (int i = 15; i < 20; i++) Assert.Equal(i - 15, buffer[i]);
+ for (int i = 20; i < 25; i++) Assert.Equal(0, buffer[i]);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ReadEmptyBuffer(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] emptyBuffer = Array.Empty<byte>();
+
+ // ReadExactly on an empty buffer is a no-op
+ if (async)
+ {
+ await s.ReadExactlyAsync(emptyBuffer);
+ await s.ReadExactlyAsync(emptyBuffer, 0, 0);
+ }
+ else
+ {
+ s.ReadExactly(emptyBuffer);
+ s.ReadExactly(emptyBuffer, 0, 0);
+ }
+
+ Assert.Equal(0, readInvokedCount);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task ThrowOnEndOfStream(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ if (readInvokedCount == 1)
+ {
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ }
+ else
+ {
+ return 0;
+ }
+ });
+
+ byte[] buffer = new byte[11];
+ if (async)
+ {
+ await Assert.ThrowsAsync<EndOfStreamException>(async () => await s.ReadExactlyAsync(buffer));
+ }
+ else
+ {
+ Assert.Throws<EndOfStreamException>(() => s.ReadExactly(buffer));
+ }
+ Assert.Equal(2, readInvokedCount);
+
+ readInvokedCount = 0;
+ if (async)
+ {
+ await Assert.ThrowsAsync<EndOfStreamException>(async () => await s.ReadExactlyAsync(buffer, 0, buffer.Length));
+ }
+ else
+ {
+ Assert.Throws<EndOfStreamException>(() => s.ReadExactly(buffer, 0, buffer.Length));
+ }
+ Assert.Equal(2, readInvokedCount);
+ }
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task OffsetCount_ArgumentChecking(bool async)
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readFunc: (array, offset, count) =>
+ {
+ readInvokedCount++;
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return byteCount;
+ });
+
+ byte[] buffer = new byte[30];
+
+ if (async)
+ {
+ await Assert.ThrowsAsync<ArgumentNullException>(async () => await s.ReadExactlyAsync(null, 0, 1));
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadExactlyAsync(buffer, 0, -1));
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadExactlyAsync(buffer, -1, buffer.Length));
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadExactlyAsync(buffer, buffer.Length, 1));
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadExactlyAsync(buffer, 0, buffer.Length + 1));
+ await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await s.ReadExactlyAsync(buffer, buffer.Length - 1, 2));
+ }
+ else
+ {
+ Assert.Throws<ArgumentNullException>(() => s.ReadExactly(null, 0, 1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadExactly(buffer, 0, -1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadExactly(buffer, -1, buffer.Length));
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadExactly(buffer, buffer.Length, 1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadExactly(buffer, 0, buffer.Length + 1));
+ Assert.Throws<ArgumentOutOfRangeException>(() => s.ReadExactly(buffer, buffer.Length - 1, 2));
+ }
+
+ Assert.Equal(0, readInvokedCount);
+ }
+
+ [Fact]
+ public async Task CancellationTokenIsPassedThrough()
+ {
+ int readInvokedCount = 0;
+ var s = new DelegateStream(
+ canReadFunc: () => true,
+ readAsyncFunc: (array, offset, count, cancellationToken) =>
+ {
+ readInvokedCount++;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ int byteCount = Math.Min(count, 10);
+ for (int i = 0; i < byteCount; i++) array[offset + i] = (byte)i;
+ return Task.FromResult(10);
+ });
+
+ byte[] buffer = new byte[20];
+
+ using CancellationTokenSource cts = new CancellationTokenSource();
+ CancellationToken token = cts.Token;
+ cts.Cancel();
+
+ await Assert.ThrowsAsync<OperationCanceledException>(async () => await s.ReadExactlyAsync(buffer, cancellationToken: token));
+ await Assert.ThrowsAsync<OperationCanceledException>(async () => await s.ReadExactlyAsync(buffer, 0, buffer.Length, cancellationToken: token));
+ Assert.Equal(2, readInvokedCount);
+ }
+ }
+}
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>System.IO</RootNamespace>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Compile Include="StreamWriter\StreamWriter.DisposeAsync.cs" />
<Compile Include="StreamWriter\StreamWriter.FlushTests.cs" />
<Compile Include="StreamWriter\StreamWriter.WriteTests.cs" />
+ <Compile Include="Stream\Stream.ReadAtLeast.cs" />
+ <Compile Include="Stream\Stream.ReadExactly.cs" />
<Compile Include="Stream\Stream.DisposeAsync.cs" />
<Compile Include="Stream\Stream.ReadWriteSpan.cs" />
<Compile Include="Stream\Stream.NullTests.cs" />
private static async ValueTask ReadToFillAsync(Stream stream, Memory<byte> 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);
}
}
}
private async Task ProcessSocks4Request(Socket clientSocket, NetworkStream ns)
{
byte[] buffer = new byte[7];
- await ReadToFillAsync(ns, buffer).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer).ConfigureAwait(false);
if (buffer[0] != 1)
throw new Exception("Only CONNECT is supported.");
throw new Exception("Early EOF");
byte[] buffer = new byte[1024];
- await ReadToFillAsync(ns, buffer.AsMemory(0, nMethods)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, nMethods)).ConfigureAwait(false);
byte expectedAuthMethod = _username == null ? (byte)0 : (byte)2;
if (!buffer.AsSpan(0, nMethods).Contains(expectedAuthMethod))
throw new Exception("Bad subnegotiation version.");
int usernameLength = await ns.ReadByteAsync().ConfigureAwait(false);
- await ReadToFillAsync(ns, buffer.AsMemory(0, usernameLength)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, usernameLength)).ConfigureAwait(false);
string username = Encoding.UTF8.GetString(buffer.AsSpan(0, usernameLength));
int passwordLength = await ns.ReadByteAsync().ConfigureAwait(false);
- await ReadToFillAsync(ns, buffer.AsMemory(0, passwordLength)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, passwordLength)).ConfigureAwait(false);
string password = Encoding.UTF8.GetString(buffer.AsSpan(0, passwordLength));
if (username != _username || password != _password)
await ns.WriteAsync(new byte[] { 1, 0 }).ConfigureAwait(false);
}
- await ReadToFillAsync(ns, buffer.AsMemory(0, 4)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, 4)).ConfigureAwait(false);
if (buffer[0] != 5)
throw new Exception("Bad protocol version.");
if (buffer[1] != 1)
switch (buffer[3])
{
case 1:
- await ReadToFillAsync(ns, buffer.AsMemory(0, 4)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, 4)).ConfigureAwait(false);
remoteHost = new IPAddress(buffer.AsSpan(0, 4)).ToString();
break;
case 4:
- await ReadToFillAsync(ns, buffer.AsMemory(0, 16)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, 16)).ConfigureAwait(false);
remoteHost = new IPAddress(buffer.AsSpan(0, 16)).ToString();
break;
case 3:
int length = await ns.ReadByteAsync().ConfigureAwait(false);
if (length == -1)
throw new Exception("Early EOF");
- await ReadToFillAsync(ns, buffer.AsMemory(0, length)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, length)).ConfigureAwait(false);
remoteHost = Encoding.UTF8.GetString(buffer.AsSpan(0, length));
break;
throw new Exception("Unknown address type.");
}
- await ReadToFillAsync(ns, buffer.AsMemory(0, 2)).ConfigureAwait(false);
+ await ns.ReadExactlyAsync(buffer.AsMemory(0, 2)).ConfigureAwait(false);
int port = (buffer[0] << 8) + buffer[1];
await ns.WriteAsync(new byte[] { 5, 0, 0, 1, 0, 0, 0, 0, 0, 0 }).ConfigureAwait(false);
}
}
- private async ValueTask ReadToFillAsync(Stream stream, Memory<byte> buffer)
- {
- while (!buffer.IsEmpty)
- {
- int bytesRead = await stream.ReadAsync(buffer).ConfigureAwait(false);
- if (bytesRead == 0)
- throw new Exception("Incomplete request");
-
- buffer = buffer.Slice(bytesRead);
- }
- }
-
public async ValueTask DisposeAsync()
{
_listener.Dispose();
static async ValueTask<int> ReadAllAsync(Stream stream, Memory<byte> 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;
}
internal interface IReadWriteAdapter
{
static abstract ValueTask<int> ReadAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken);
+ static abstract ValueTask<int> ReadAtLeastAsync(Stream stream, Memory<byte> 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<bool> waiter);
public static ValueTask<int> ReadAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken) =>
stream.ReadAsync(buffer, cancellationToken);
+ public static ValueTask<int> ReadAtLeastAsync(Stream stream, Memory<byte> 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<byte>(buffer, offset, count), cancellationToken);
public static ValueTask<int> ReadAsync(Stream stream, Memory<byte> buffer, CancellationToken cancellationToken) =>
new ValueTask<int>(stream.Read(buffer.Span));
+ public static ValueTask<int> ReadAtLeastAsync(Stream stream, Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken) =>
+ new ValueTask<int>(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);
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);
buffer = new byte[_curReadHeader.PayloadSize];
- offset = 0;
- while (offset < buffer.Length)
+ if (buffer.Length > 0)
{
- bytesRead = await TAdapter.ReadAsync(stream, buffer.AsMemory(offset), cancellationToken).ConfigureAwait(false);
- if (bytesRead == 0)
+ bytesRead = await TAdapter.ReadAtLeastAsync(
+ stream, buffer, buffer.Length, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false);
+ if (bytesRead < buffer.Length)
{
throw new IOException(SR.Format(SR.net_io_readfailure, SR.net_io_connectionclosed));
}
-
- offset += bytesRead;
}
return buffer;
}
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<byte> 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;
}
totalBytesReceived += numBytesRead;
}
_receiveBufferOffset = 0;
// While we don't have enough data, read more.
- while (_receiveBufferCount < minimumRequiredBytes)
+ if (_receiveBufferCount < 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)
+ int bytesToRead = minimumRequiredBytes - _receiveBufferCount;
+ int numRead = await _stream.ReadAtLeastAsync(
+ _receiveBuffer.Slice(_receiveBufferCount), bytesToRead, throwOnEndOfStream: false, cancellationToken).ConfigureAwait(false);
+ _receiveBufferCount += numRead;
+
+ if (numRead < bytesToRead)
{
ThrowEOFUnexpected();
- break;
}
- _receiveBufferCount += numRead;
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
- <!--
- Microsoft ResX Schema
-
+ <!--
+ Microsoft ResX Schema
+
Version 2.0
-
- The primary goals of this format is to allow a simple XML format
- that is mostly human readable. The generation and parsing of the
- various data types are done through the TypeConverter classes
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
associated with the data types.
-
+
Example:
-
+
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
-
- There are any number of "resheader" rows that contain simple
+
+ There are any number of "resheader" rows that contain simple
name/value pairs.
-
- Each data row contains a name, and value. The row also contains a
- type or mimetype. Type corresponds to a .NET class that support
- text/value conversion through the TypeConverter architecture.
- Classes that don't support this are serialized and stored with the
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
mimetype set.
-
- The mimetype is used for serialized objects, and tells the
- ResXResourceReader how to depersist the object. This is currently not
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
-
- Note - application/x-microsoft.net.object.binary.base64 is the format
- that the ResXResourceWriter will generate, however the reader can
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
-
+
mimetype: application/x-microsoft.net.object.binary.base64
- value : The object must be serialized with
+ value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
-
+
mimetype: application/x-microsoft.net.object.soap.base64
- value : The object must be serialized with
+ value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
- value : The object must be serialized into a byte array
+ value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<data name="InvalidOperation_ComInteropRequireComWrapperInstance" xml:space="preserve">
<value>COM Interop requires ComWrapper instance registered for marshalling.</value>
</data>
-</root>
+ <data name="ArgumentOutOfRange_NotGreaterThanBufferLength" xml:space="preserve">
+ <value>Must not be greater than the length of the buffer.</value>
+ </data>
+</root>
\ No newline at end of file
}
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)
{
{
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;
}
throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_BinaryReaderFillBuffer);
}
- int bytesRead = 0;
- int n;
-
ThrowIfDisposed();
// Need to find a good threshold for calling ReadByte() repeatedly
// streams.
if (numBytes == 1)
{
- n = _stream.ReadByte();
+ int n = _stream.ReadByte();
if (n == -1)
{
ThrowHelper.ThrowEndOfFileException();
return;
}
- do
+ if (numBytes > 0)
{
- n = _stream.Read(_buffer, bytesRead, numBytes - bytesRead);
+ _stream.ReadExactly(_buffer.AsSpan(0, numBytes));
+ }
+ else
+ {
+ // ReadExactly no-ops for empty buffers, so special case numBytes == 0 to preserve existing behavior.
+ int n = _stream.Read(_buffer, 0, 0);
if (n == 0)
{
ThrowHelper.ThrowEndOfFileException();
}
- bytesRead += n;
- } while (bytesRead < numBytes);
+ }
}
public int Read7BitEncodedInt()
}
}
+ /// <summary>
+ /// Asynchronously reads bytes from the current stream, advances the position within the stream until the <paramref name="buffer"/> is filled,
+ /// and monitors cancellation requests.
+ /// </summary>
+ /// <param name="buffer">The buffer to write the data into.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ /// <returns>A task that represents the asynchronous read operation.</returns>
+ /// <exception cref="EndOfStreamException">
+ /// The end of the stream is reached before filling the <paramref name="buffer"/>.
+ /// </exception>
+ /// <remarks>
+ /// When <paramref name="buffer"/> is empty, this read operation will be completed without waiting for available data in the stream.
+ /// </remarks>
+ public ValueTask ReadExactlyAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
+ {
+ ValueTask<int> vt = ReadAtLeastAsyncCore(buffer, buffer.Length, throwOnEndOfStream: true, cancellationToken);
+
+ // transfer the ValueTask<int> to a ValueTask without allocating here.
+ return ValueTask.DangerousCreateFromTypedValueTask(vt);
+ }
+
+ /// <summary>
+ /// Asynchronously reads <paramref name="count"/> number of bytes from the current stream, advances the position within the stream,
+ /// and monitors cancellation requests.
+ /// </summary>
+ /// <param name="buffer">The buffer to write the data into.</param>
+ /// <param name="offset">The byte offset in <paramref name="buffer"/> at which to begin writing data from the stream.</param>
+ /// <param name="count">The number of bytes to be read from the current stream.</param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ /// <returns>A task that represents the asynchronous read operation.</returns>
+ /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="offset"/> is outside the bounds of <paramref name="buffer"/>.
+ /// -or-
+ /// <paramref name="count"/> is negative.
+ /// -or-
+ /// The range specified by the combination of <paramref name="offset"/> and <paramref name="count"/> exceeds the
+ /// length of <paramref name="buffer"/>.
+ /// </exception>
+ /// <exception cref="EndOfStreamException">
+ /// The end of the stream is reached before reading <paramref name="count"/> number of bytes.
+ /// </exception>
+ /// <remarks>
+ /// When <paramref name="count"/> is 0 (zero), this read operation will be completed without waiting for available data in the stream.
+ /// </remarks>
+ public ValueTask ReadExactlyAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
+ {
+ ValidateBufferArguments(buffer, offset, count);
+
+ ValueTask<int> vt = ReadAtLeastAsyncCore(buffer.AsMemory(offset, count), count, throwOnEndOfStream: true, cancellationToken);
+
+ // transfer the ValueTask<int> to a ValueTask without allocating here.
+ return ValueTask.DangerousCreateFromTypedValueTask(vt);
+ }
+
+ /// <summary>
+ /// Asynchronously reads at least a minimum number of bytes from the current stream, advances the position within the stream by the
+ /// number of bytes read, and monitors cancellation requests.
+ /// </summary>
+ /// <param name="buffer">The region of memory to write the data into.</param>
+ /// <param name="minimumBytes">The minimum number of bytes to read into the buffer.</param>
+ /// <param name="throwOnEndOfStream">
+ /// <see langword="true"/> to throw an exception if the end of the stream is reached before reading <paramref name="minimumBytes"/> of bytes;
+ /// <see langword="false"/> to return less than <paramref name="minimumBytes"/> when the end of the stream is reached.
+ /// The default is <see langword="true"/>.
+ /// </param>
+ /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+ /// <returns>
+ /// A task that represents the asynchronous read operation. The value of its <see cref="ValueTask{TResult}.Result"/> property contains the
+ /// total number of bytes read into the buffer. This is guaranteed to be greater than or equal to <paramref name="minimumBytes"/> when
+ /// <paramref name="throwOnEndOfStream"/> is <see langword="true"/>. This will be less than <paramref name="minimumBytes"/> when the end
+ /// of the stream is reached and <paramref name="throwOnEndOfStream"/> is <see langword="false"/>. This can be less than the number of
+ /// bytes allocated in the buffer if that many bytes are not currently available.
+ /// </returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="minimumBytes"/> is negative, or is greater than the length of <paramref name="buffer"/>.
+ /// </exception>
+ /// <exception cref="EndOfStreamException">
+ /// <paramref name="throwOnEndOfStream"/> is <see langword="true"/> and the end of the stream is reached before reading
+ /// <paramref name="minimumBytes"/> bytes of data.
+ /// </exception>
+ /// <remarks>
+ /// When <paramref name="minimumBytes"/> is 0 (zero), this read operation will be completed without waiting for available data in the stream.
+ /// </remarks>
+ public ValueTask<int> ReadAtLeastAsync(Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default)
+ {
+ ValidateReadAtLeastArguments(buffer.Length, minimumBytes);
+
+ return ReadAtLeastAsyncCore(buffer, minimumBytes, throwOnEndOfStream, cancellationToken);
+ }
+
+ // No argument checking is done here. It is up to the caller.
+ [AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
+ private async ValueTask<int> ReadAtLeastAsyncCore(Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream, CancellationToken cancellationToken)
+ {
+ Debug.Assert(minimumBytes <= buffer.Length);
+
+ int totalRead = 0;
+ while (totalRead < minimumBytes)
+ {
+ int read = await ReadAsync(buffer.Slice(totalRead), cancellationToken).ConfigureAwait(false);
+ if (read == 0)
+ {
+ if (throwOnEndOfStream)
+ {
+ ThrowHelper.ThrowEndOfFileException();
+ }
+
+ return totalRead;
+ }
+
+ totalRead += read;
+ }
+
+ return totalRead;
+ }
+
#if NATIVEAOT // TODO: https://github.com/dotnet/corert/issues/3251
private static bool HasOverriddenBeginEndRead() => true;
return r == 0 ? -1 : oneByteArray[0];
}
+ /// <summary>
+ /// Reads bytes from the current stream and advances the position within the stream until the <paramref name="buffer"/> is filled.
+ /// </summary>
+ /// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced by the bytes read from the current stream.</param>
+ /// <exception cref="EndOfStreamException">
+ /// The end of the stream is reached before filling the <paramref name="buffer"/>.
+ /// </exception>
+ /// <remarks>
+ /// When <paramref name="buffer"/> is empty, this read operation will be completed without waiting for available data in the stream.
+ /// </remarks>
+ public void ReadExactly(Span<byte> buffer) =>
+ _ = ReadAtLeastCore(buffer, buffer.Length, throwOnEndOfStream: true);
+
+ /// <summary>
+ /// Reads <paramref name="count"/> number of bytes from the current stream and advances the position within the stream.
+ /// </summary>
+ /// <param name="buffer">
+ /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values
+ /// between <paramref name="offset"/> and (<paramref name="offset"/> + <paramref name="count"/> - 1) replaced
+ /// by the bytes read from the current stream.
+ /// </param>
+ /// <param name="offset">The byte offset in <paramref name="buffer"/> at which to begin storing the data read from the current stream.</param>
+ /// <param name="count">The number of bytes to be read from the current stream.</param>
+ /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is <see langword="null"/>.</exception>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="offset"/> is outside the bounds of <paramref name="buffer"/>.
+ /// -or-
+ /// <paramref name="count"/> is negative.
+ /// -or-
+ /// The range specified by the combination of <paramref name="offset"/> and <paramref name="count"/> exceeds the
+ /// length of <paramref name="buffer"/>.
+ /// </exception>
+ /// <exception cref="EndOfStreamException">
+ /// The end of the stream is reached before reading <paramref name="count"/> number of bytes.
+ /// </exception>
+ /// <remarks>
+ /// When <paramref name="count"/> is 0 (zero), this read operation will be completed without waiting for available data in the stream.
+ /// </remarks>
+ public void ReadExactly(byte[] buffer, int offset, int count)
+ {
+ ValidateBufferArguments(buffer, offset, count);
+
+ _ = ReadAtLeastCore(buffer.AsSpan(offset, count), count, throwOnEndOfStream: true);
+ }
+
+ /// <summary>
+ /// Reads at least a minimum number of bytes from the current stream and advances the position within the stream by the number of bytes read.
+ /// </summary>
+ /// <param name="buffer">A region of memory. When this method returns, the contents of this region are replaced by the bytes read from the current stream.</param>
+ /// <param name="minimumBytes">The minimum number of bytes to read into the buffer.</param>
+ /// <param name="throwOnEndOfStream">
+ /// <see langword="true"/> to throw an exception if the end of the stream is reached before reading <paramref name="minimumBytes"/> of bytes;
+ /// <see langword="false"/> to return less than <paramref name="minimumBytes"/> when the end of the stream is reached.
+ /// The default is <see langword="true"/>.
+ /// </param>
+ /// <returns>
+ /// The total number of bytes read into the buffer. This is guaranteed to be greater than or equal to <paramref name="minimumBytes"/>
+ /// when <paramref name="throwOnEndOfStream"/> is <see langword="true"/>. This will be less than <paramref name="minimumBytes"/> when the
+ /// end of the stream is reached and <paramref name="throwOnEndOfStream"/> is <see langword="false"/>. This can be less than the number
+ /// of bytes allocated in the buffer if that many bytes are not currently available.
+ /// </returns>
+ /// <exception cref="ArgumentOutOfRangeException">
+ /// <paramref name="minimumBytes"/> is negative, or is greater than the length of <paramref name="buffer"/>.
+ /// </exception>
+ /// <exception cref="EndOfStreamException">
+ /// <paramref name="throwOnEndOfStream"/> is <see langword="true"/> and the end of the stream is reached before reading
+ /// <paramref name="minimumBytes"/> bytes of data.
+ /// </exception>
+ /// <remarks>
+ /// When <paramref name="minimumBytes"/> is 0 (zero), this read operation will be completed without waiting for available data in the stream.
+ /// </remarks>
+ public int ReadAtLeast(Span<byte> 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<byte> buffer, int minimumBytes, bool throwOnEndOfStream)
+ {
+ Debug.Assert(minimumBytes <= buffer.Length);
+
+ int totalRead = 0;
+ while (totalRead < minimumBytes)
+ {
+ int read = Read(buffer.Slice(totalRead));
+ if (read == 0)
+ {
+ if (throwOnEndOfStream)
+ {
+ ThrowHelper.ThrowEndOfFileException();
+ }
+
+ return totalRead;
+ }
+
+ totalRead += read;
+ }
+
+ return totalRead;
+ }
+
public abstract void Write(byte[] buffer, int offset, int count);
public virtual void Write(ReadOnlySpan<byte> buffer)
}
}
+ private static void ValidateReadAtLeastArguments(int bufferLength, int minimumBytes)
+ {
+ if (minimumBytes < 0)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.minimumBytes, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum);
+ }
+
+ if (bufferLength < minimumBytes)
+ {
+ ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.minimumBytes, ExceptionResource.ArgumentOutOfRange_NotGreaterThanBufferLength);
+ }
+ }
+
/// <summary>Validates arguments provided to the <see cref="CopyTo(Stream, int)"/> or <see cref="CopyToAsync(Stream, int, CancellationToken)"/> methods.</summary>
/// <param name="destination">The <see cref="Stream"/> "destination" argument passed to the copy method.</param>
/// <param name="bufferSize">The integer "bufferSize" argument passed to the copy method.</param>
}
}
+ /// <summary>
+ /// Transfers the <see cref="ValueTask{TResult}"/> to a <see cref="ValueTask"/> instance.
+ ///
+ /// The <see cref="ValueTask{TResult}"/> should not be used after calling this method.
+ /// </summary>
+ internal static ValueTask DangerousCreateFromTypedValueTask<TResult>(ValueTask<TResult> valueTask)
+ {
+ Debug.Assert(valueTask._obj is null or Task or IValueTaskSource, "If the ValueTask<>'s backing object is an IValueTaskSource<TResult>, it must also be IValueTaskSource.");
+
+ return new ValueTask(valueTask._obj, valueTask._token, valueTask._continueOnCapturedContext);
+ }
+
/// <summary>Gets an awaiter for this <see cref="ValueTask"/>.</summary>
public ValueTaskAwaiter GetAwaiter() => new ValueTaskAwaiter(in this);
}
[DoesNotReturn]
- internal static void ThrowArgumentOutOfRangeException_NeedPosNum(string? paramName)
- {
- throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_NeedPosNum);
- }
-
- [DoesNotReturn]
internal static void ThrowArgumentOutOfRangeException_NeedNonNegNum(string paramName)
{
throw new ArgumentOutOfRangeException(paramName, SR.ArgumentOutOfRange_NeedNonNegNum);
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 "";
return SR.CancellationTokenSource_Disposed;
case ExceptionResource.Argument_AlignmentMustBePow2:
return SR.Argument_AlignmentMustBePow2;
+ case ExceptionResource.ArgumentOutOfRange_NotGreaterThanBufferLength:
+ return SR.ArgumentOutOfRange_NotGreaterThanBufferLength;
default:
Debug.Fail("The enum value is not defined, please check the ExceptionResource Enum.");
return "";
stream,
anyOf,
overlapped,
+ minimumBytes,
}
//
ArgumentOutOfRange_GetCharCountOverflow,
ArgumentOutOfRange_ListInsert,
ArgumentOutOfRange_NeedNonNegNum,
+ ArgumentOutOfRange_NotGreaterThanBufferLength,
ArgumentOutOfRange_SmallCapacity,
Argument_InvalidOffLen,
Argument_CannotExtractScalar,
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);
}
}
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);
}
}
}
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);
}
// 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)
// 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)
+ if (_ps.bytesUsed < 4 && _ps.bytes.Length - _ps.bytesUsed > 0)
{
- int read = stream.Read(_ps.bytes, _ps.bytesUsed, _ps.bytes.Length - _ps.bytesUsed);
- if (read == 0)
+ int bytesToRead = Math.Min(4, _ps.bytes.Length - _ps.bytesUsed);
+ int read = stream.ReadAtLeast(_ps.bytes.AsSpan(_ps.bytesUsed), bytesToRead, throwOnEndOfStream: false);
+ if (read < bytesToRead)
{
_ps.isStreamEof = true;
- break;
}
_ps.bytesUsed += read;
}
// 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)
+ if (_ps.bytesUsed < 4 && _ps.bytes.Length - _ps.bytesUsed > 0)
{
- int read = await stream.ReadAsync(_ps.bytes.AsMemory(_ps.bytesUsed)).ConfigureAwait(false);
- if (read == 0)
+ int bytesToRead = Math.Min(4, _ps.bytes.Length - _ps.bytesUsed);
+ 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;
}
#if NETCOREAPP
internal static int TryReadAll(this Stream stream, Span<byte> buffer)
+#if NET7_0_OR_GREATER
+ => stream.ReadAtLeast(buffer, buffer.Length, throwOnEndOfStream: false);
+#else
{
int totalBytesRead = 0;
while (totalBytesRead < buffer.Length)
return totalBytesRead;
}
#endif
+#endif
/// <summary>
/// Resolve image size as either the given user-specified size or distance from current position to end-of-stream.
public System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count) { throw null; }
public virtual System.Threading.Tasks.Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken) { throw null; }
public virtual System.Threading.Tasks.ValueTask<int> ReadAsync(System.Memory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
+ public int ReadAtLeast(System.Span<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true) { throw null; }
+ public System.Threading.Tasks.ValueTask<int> ReadAtLeastAsync(System.Memory<byte> 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<byte> 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<byte> 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; }