diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs index d22a70fbbdf68962331bfc7fdb2c9fd00a990e80..fd9e04190fbf6d126cf8b7f84f63ff06a9d67003 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs @@ -251,7 +251,7 @@ public async Task ShutdownAsync(bool failCurrentRequest = false) long firstInvalidStreamId = failCurrentRequest ? _currentStreamId : _currentStreamId + 4; await _outboundControlStream.SendGoAwayFrameAsync(firstInvalidStreamId); } - catch (QuicConnectionAbortedException abortException) when (abortException.ErrorCode == H3_NO_ERROR) + catch (QuicException abortException) when (abortException.QuicError == QuicError.ConnectionAborted && abortException.ApplicationErrorCode == H3_NO_ERROR) { // Client must have closed the connection already because the HttpClientHandler instance was disposed. // So nothing to do. @@ -288,7 +288,7 @@ public async Task WaitForClientDisconnectAsync(bool refuseNewRequests = true) throw new Exception("Unexpected request stream received while waiting for client disconnect"); } } - catch (QuicConnectionAbortedException abortException) when (abortException.ErrorCode == H3_NO_ERROR) + catch (QuicException abortException) when (abortException.QuicError == QuicError.ConnectionAborted && abortException.ApplicationErrorCode == H3_NO_ERROR) { break; } @@ -301,7 +301,8 @@ public async Task WaitForClientDisconnectAsync(bool refuseNewRequests = true) // The client's control stream should throw QuicConnectionAbortedException, indicating that it was // aborted because the connection was closed (and was not explicitly closed or aborted prior to the connection being closed) - await Assert.ThrowsAsync(async () => await _inboundControlStream.ReadFrameAsync()); + QuicException ex = await Assert.ThrowsAsync(async () => await _inboundControlStream.ReadFrameAsync()); + Assert.Equal(QuicError.ConnectionAborted, ex.QuicError); await CloseAsync(H3_NO_ERROR); } diff --git a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs index 9491c563668f75cfbab7e93f04fb21eb2cec9fb8..b3d7b7f5644230fed3b49d98225e584e76f0422a 100644 --- a/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs +++ b/src/libraries/Common/tests/System/Net/Http/Http3LoopbackStream.cs @@ -379,7 +379,7 @@ async Task WaitForReadCancellation() } } } - catch (QuicStreamAbortedException ex) when (ex.ErrorCode == Http3LoopbackConnection.H3_REQUEST_CANCELLED) + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted && ex.ApplicationErrorCode == Http3LoopbackConnection.H3_REQUEST_CANCELLED) { readCanceled = true; } @@ -391,7 +391,7 @@ async Task WaitForWriteCancellation() { await _stream.WaitForWriteCompletionAsync(); } - catch (QuicStreamAbortedException ex) when (ex.ErrorCode == Http3LoopbackConnection.H3_REQUEST_CANCELLED) + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted && ex.ApplicationErrorCode == Http3LoopbackConnection.H3_REQUEST_CANCELLED) { writeCanceled = true; } diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs index 24438ffa02e3da08d45fcff09ac40f2a97bb8f89..3201d5de9247257c09e0af89607a68b3ece24352 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs @@ -196,7 +196,7 @@ public async Task SendAsync(HttpRequestMessage request, lon // Swallow any exceptions caused by the connection being closed locally or even disposed due to a race. // Since quicStream will stay `null`, the code below will throw appropriate exception to retry the request. catch (ObjectDisposedException) { } - catch (QuicException e) when (!(e is QuicConnectionAbortedException)) { } + catch (QuicException e) when (e.QuicError != QuicError.OperationAborted) { } finally { if (HttpTelemetry.Log.IsEnabled() && queueStartingTimestamp != 0) @@ -232,11 +232,13 @@ public async Task SendAsync(HttpRequestMessage request, lon return await responseTask.ConfigureAwait(false); } - catch (QuicConnectionAbortedException ex) + catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted) { + Debug.Assert(ex.ApplicationErrorCode.HasValue); + // This will happen if we aborted _connection somewhere. Abort(ex); - throw new HttpRequestException(SR.Format(SR.net_http_http3_connection_error, ex.ErrorCode), ex, RequestRetryType.RetryOnConnectionFailure); + throw new HttpRequestException(SR.Format(SR.net_http_http3_connection_error, ex.ApplicationErrorCode.Value), ex, RequestRetryType.RetryOnConnectionFailure); } finally { @@ -417,7 +419,7 @@ private async Task AcceptStreamsAsync() _ = ProcessServerStreamAsync(stream); } } - catch (QuicOperationAbortedException) + catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted) { // Shutdown initiated by us, no need to abort. } @@ -452,7 +454,7 @@ await using (stream.ConfigureAwait(false)) { bytesRead = await stream.ReadAsync(buffer.AvailableMemory, CancellationToken.None).ConfigureAwait(false); } - catch (QuicStreamAbortedException) + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted) { // Treat identical to receiving 0. See below comment. bytesRead = 0; diff --git a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs index a373bac73eb2dbdecfb853f0115ec46f545e8120..58a2159d3a0073bf8c4dec559d3a17787a24bcd8 100644 --- a/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs +++ b/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3RequestStream.cs @@ -228,23 +228,27 @@ public async Task SendAsync(CancellationToken cancellationT shouldCancelBody = false; return response; } - catch (QuicStreamAbortedException ex) when (ex.ErrorCode == (long)Http3ErrorCode.VersionFallback) + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted) { - // The server is requesting us fall back to an older HTTP version. - throw new HttpRequestException(SR.net_http_retry_on_older_version, ex, RequestRetryType.RetryOnLowerHttpVersion); - } - catch (QuicStreamAbortedException ex) when (ex.ErrorCode == (long)Http3ErrorCode.RequestRejected) - { - // The server is rejecting the request without processing it, retry it on a different connection. - throw new HttpRequestException(SR.net_http_request_aborted, ex, RequestRetryType.RetryOnConnectionFailure); - } - catch (QuicStreamAbortedException ex) - { - // Our stream was reset. - Exception? abortException = _connection.AbortException; - throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex); + Debug.Assert(ex.ApplicationErrorCode.HasValue); + + switch ((Http3ErrorCode)ex.ApplicationErrorCode.Value) + { + case Http3ErrorCode.VersionFallback: + // The server is requesting us fall back to an older HTTP version. + throw new HttpRequestException(SR.net_http_retry_on_older_version, ex, RequestRetryType.RetryOnLowerHttpVersion); + + case Http3ErrorCode.RequestRejected: + // The server is rejecting the request without processing it, retry it on a different connection. + throw new HttpRequestException(SR.net_http_request_aborted, ex, RequestRetryType.RetryOnConnectionFailure); + + default: + // Our stream was reset. + Exception? abortException = _connection.AbortException; + throw new HttpRequestException(SR.net_http_client_execution_error, abortException ?? ex); + } } - catch (QuicConnectionAbortedException ex) + catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted) { // Our connection was reset. Start shutting down the connection. Exception abortException = _connection.Abort(ex); @@ -1185,12 +1189,10 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken { switch (ex) { - // Peer aborted the stream - case QuicStreamAbortedException: - // User aborted the stream - case QuicOperationAbortedException: + case QuicException e when (e.QuicError == QuicError.StreamAborted || e.QuicError == QuicError.OperationAborted): + // Peer or user aborted the stream throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, ex)); - case QuicConnectionAbortedException: + case QuicException e when (e.QuicError == QuicError.ConnectionAborted): // Our connection was reset. Start aborting the connection. Exception abortException = _connection.Abort(ex); throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, abortException)); @@ -1202,10 +1204,10 @@ private void HandleReadResponseContentException(Exception ex, CancellationToken _stream.AbortRead((long)Http3ErrorCode.RequestCancelled); ExceptionDispatchInfo.Throw(ex); // Rethrow. return; // Never reached. - default: - _stream.AbortRead((long)Http3ErrorCode.InternalError); - throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, ex)); } + + _stream.AbortRead((long)Http3ErrorCode.InternalError); + throw new IOException(SR.net_http_client_execution_error, new HttpRequestException(SR.net_http_client_execution_error, ex)); } private async ValueTask ReadNextDataFrameAsync(HttpResponseMessage response, CancellationToken cancellationToken) diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs index 9d31101a2c153ade7aff5394571af2949221b390..1973bdb361b3b9078078fa5638413d720533240e 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Headers.cs @@ -277,18 +277,18 @@ public async Task GetAsync_EmptyResponseHeader_Success() [ActiveIssue("https://github.com/dotnet/runtime/issues/69870", TestPlatforms.Android)] public async Task GetAsync_MissingExpires_ReturnNull() { - await LoopbackServerFactory.CreateClientAndServerAsync(async uri => - { + await LoopbackServerFactory.CreateClientAndServerAsync(async uri => + { using (HttpClient client = CreateHttpClient()) { HttpResponseMessage response = await client.GetAsync(uri); Assert.Null(response.Content.Headers.Expires); } }, - async server => - { - await server.HandleRequestAsync(HttpStatusCode.OK); - }); + async server => + { + await server.HandleRequestAsync(HttpStatusCode.OK); + }); } [Theory] @@ -434,7 +434,6 @@ public async Task SendAsync_WithZeroLengthHeaderName_Throws() }); } catch (IOException) { } - catch (QuicConnectionAbortedException) { } }); } diff --git a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs index f7346ad05801bef855990462cd3ef9c530cf3184..8d7c6987f53f9d6df8af4e435840fdf07adc7889 100644 --- a/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs +++ b/src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs @@ -12,6 +12,7 @@ using System.Net.Sockets; using System.Net.Test.Common; using System.Reflection; +using System.Security.Authentication; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -277,13 +278,13 @@ public async Task ReservedFrameType_Throws() await stream.SendFrameAsync(ReservedHttp2PriorityFrameId, new byte[8]); - QuicConnectionAbortedException ex = await Assert.ThrowsAsync(async () => + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, async () => { await stream.HandleRequestAsync(); using Http3LoopbackStream stream2 = await connection.AcceptRequestStreamAsync(); }); - Assert.Equal(UnexpectedFrameErrorCode, ex.ErrorCode); + Assert.Equal(UnexpectedFrameErrorCode, ex.ApplicationErrorCode); }); Task clientTask = Task.Run(async () => @@ -325,13 +326,13 @@ public async Task RequestSentResponseDisposed_ThrowsOnServer() { await stream.SendResponseBodyAsync(data, isFinal: false); } - catch (QuicStreamAbortedException) + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted) { hasFailed = true; break; } } - Assert.True(hasFailed, $"Expected {nameof(QuicStreamAbortedException)}, instead ran successfully for {sw.Elapsed}"); + Assert.True(hasFailed, $"Expected {nameof(QuicException)} with {nameof(QuicError.StreamAborted)}, instead ran successfully for {sw.Elapsed}"); }); Task clientTask = Task.Run(async () => @@ -384,13 +385,13 @@ public async Task RequestSendingResponseDisposed_ThrowsOnServer() var (frameType, payload) = await stream.ReadFrameAsync(); Assert.Equal(Http3LoopbackStream.DataFrame, frameType); } - catch (QuicStreamAbortedException) + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted) { hasFailed = true; break; } } - Assert.True(hasFailed, $"Expected {nameof(QuicStreamAbortedException)}, instead ran successfully for {sw.Elapsed}"); + Assert.True(hasFailed, $"Expected {nameof(QuicException)} with {nameof(QuicError.StreamAborted)}, instead ran successfully for {sw.Elapsed}"); }); Task clientTask = Task.Run(async () => @@ -682,8 +683,8 @@ public async Task ResponseCancellation_ServerReceivesCancellation(CancellationTy // In that case even with synchronization via semaphores, first writes after peer aborting may "succeed" (get SEND_COMPLETE event) // We are asserting that PEER_RECEIVE_ABORTED would still arrive eventually - var ex = await Assert.ThrowsAsync(() => SendDataForever(stream).WaitAsync(TimeSpan.FromSeconds(10))); - Assert.Equal(268, ex.ErrorCode); + var ex = await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => SendDataForever(stream).WaitAsync(TimeSpan.FromSeconds(10))); + Assert.Equal(268, ex.ApplicationErrorCode); serverDone.Release(); }); @@ -724,7 +725,8 @@ public async Task ResponseCancellation_ServerReceivesCancellation(CancellationTy { var ioe = Assert.IsType(ex); var hre = Assert.IsType(ioe.InnerException); - Assert.IsType(hre.InnerException); + var qex = Assert.IsType(hre.InnerException); + Assert.Equal(QuicError.OperationAborted, qex.QuicError); } clientDone.Release(); @@ -762,9 +764,9 @@ public async Task ResponseCancellation_BothCancellationTokenAndDispose_Success() // In that case even with synchronization via semaphores, first writes after peer aborting may "succeed" (get SEND_COMPLETE event) // We are asserting that PEER_RECEIVE_ABORTED would still arrive eventually - var ex = await Assert.ThrowsAsync(() => SendDataForever(stream).WaitAsync(TimeSpan.FromSeconds(20))); + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => SendDataForever(stream).WaitAsync(TimeSpan.FromSeconds(20))); // exact error code depends on who won the race - Assert.True(ex.ErrorCode == 268 /* cancellation */ || ex.ErrorCode == 0xffffffff /* disposal */, $"Expected 268 or 0xffffffff, got {ex.ErrorCode}"); + Assert.True(ex.ApplicationErrorCode == 268 /* cancellation */ || ex.ApplicationErrorCode == 0xffffffff /* disposal */, $"Expected 268 or 0xffffffff, got {ex.ApplicationErrorCode}"); serverDone.Release(); }); @@ -797,7 +799,8 @@ public async Task ResponseCancellation_BothCancellationTokenAndDispose_Success() { var ioe = Assert.IsType(ex); var hre = Assert.IsType(ioe.InnerException); - Assert.IsType(hre.InnerException); + var qex = Assert.IsType(hre.InnerException); + Assert.Equal(QuicError.OperationAborted, qex.QuicError); } clientDone.Release(); @@ -877,7 +880,7 @@ public async Task Alpn_NonH3_NegotiationFailure() }; HttpRequestException ex = await Assert.ThrowsAsync(() => client.SendAsync(request).WaitAsync(TimeSpan.FromSeconds(10))); - Assert.Contains("ALPN_NEG_FAILURE", ex.Message); + Assert.IsType(ex.InnerException); clientDone.Release(); }); @@ -1096,8 +1099,8 @@ public async Task RequestContentStreaming_Timeout_BothClientAndServerReceiveCanc await clientTask.WaitAsync(TimeSpan.FromSeconds(120)); // server receives cancellation - var ex = await Assert.ThrowsAsync(() => serverTask.WaitAsync(TimeSpan.FromSeconds(120))); - Assert.Equal(268 /*H3_REQUEST_CANCELLED (0x10C)*/, ex.ErrorCode); + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => serverTask.WaitAsync(TimeSpan.FromSeconds(120))); + Assert.Equal(268 /*H3_REQUEST_CANCELLED (0x10C)*/, ex.ApplicationErrorCode); Assert.NotNull(serverStream); serverStream.Dispose(); @@ -1161,8 +1164,8 @@ public async Task RequestContentStreaming_Cancellation_BothClientAndServerReceiv await clientTask.WaitAsync(TimeSpan.FromSeconds(120)); // server receives cancellation - var ex = await Assert.ThrowsAsync(() => serverTask.WaitAsync(TimeSpan.FromSeconds(120))); - Assert.Equal(268 /*H3_REQUEST_CANCELLED (0x10C)*/, ex.ErrorCode); + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => serverTask.WaitAsync(TimeSpan.FromSeconds(120))); + Assert.Equal(268 /*H3_REQUEST_CANCELLED (0x10C)*/, ex.ApplicationErrorCode); Assert.NotNull(serverStream); serverStream.Dispose(); @@ -1337,6 +1340,13 @@ public async Task DuplexStreaming_AbortByServer_StreamingCancelled(bool graceful connection.Dispose(); } + private static async Task AssertThrowsQuicExceptionAsync(QuicError expectedError, Func testCode) + { + QuicException ex = await Assert.ThrowsAsync(testCode); + Assert.Equal(expectedError, ex.QuicError); + return ex; + } + public static TheoryData StatusCodesTestData() { var statuses = Enum.GetValues(typeof(HttpStatusCode)).Cast().Where(s => s >= HttpStatusCode.OK); // exclude informational diff --git a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs index 1d72607683bdb4d95af9fc0efdb70090c07aa648..ae739503f77306451387df1636e878fd5b950551 100644 --- a/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs +++ b/src/libraries/System.Net.Quic/ref/System.Net.Quic.cs @@ -32,11 +32,6 @@ public sealed partial class QuicConnection : System.IDisposable public System.Threading.Tasks.ValueTask OpenBidirectionalStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public System.Threading.Tasks.ValueTask OpenUnidirectionalStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public partial class QuicConnectionAbortedException : System.Net.Quic.QuicException - { - public QuicConnectionAbortedException(string message, long errorCode) : base (default(string)) { } - public long ErrorCode { get { throw null; } } - } public abstract partial class QuicConnectionOptions { internal QuicConnectionOptions() { } @@ -44,11 +39,27 @@ public abstract partial class QuicConnectionOptions public int MaxBidirectionalStreams { get { throw null; } set { } } public int MaxUnidirectionalStreams { get { throw null; } set { } } } - public partial class QuicException : System.Exception + public enum QuicError + { + Success = 0, + InternalError = 1, + ConnectionAborted = 2, + StreamAborted = 3, + AddressInUse = 4, + InvalidAddress = 5, + ConnectionTimeout = 6, + HostUnreachable = 7, + ConnectionRefused = 8, + VersionNegotiationError = 9, + ConnectionIdle = 10, + ProtocolError = 11, + OperationAborted = 12, + } + public sealed partial class QuicException : System.IO.IOException { - public QuicException(string? message) { } - public QuicException(string? message, System.Exception? innerException) { } - public QuicException(string? message, System.Exception? innerException, int result) { } + public QuicException(System.Net.Quic.QuicError error, long? applicationErrorCode, string message) { } + public long? ApplicationErrorCode { get { throw null; } } + public System.Net.Quic.QuicError QuicError { get { throw null; } } } public sealed partial class QuicListener : System.IAsyncDisposable { @@ -68,10 +79,6 @@ public sealed partial class QuicListenerOptions public int ListenBacklog { get { throw null; } set { } } public required System.Net.IPEndPoint ListenEndPoint { get { throw null; } set { } } } - public partial class QuicOperationAbortedException : System.Net.Quic.QuicException - { - public QuicOperationAbortedException(string message) : base (default(string)) { } - } public sealed partial class QuicServerConnectionOptions : System.Net.Quic.QuicConnectionOptions { public QuicServerConnectionOptions() { } @@ -118,9 +125,4 @@ public sealed partial class QuicStream : System.IO.Stream public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } public override void WriteByte(byte value) { } } - public partial class QuicStreamAbortedException : System.Net.Quic.QuicException - { - public QuicStreamAbortedException(string message, long errorCode) : base (default(string)) { } - public long ErrorCode { get { throw null; } } - } } diff --git a/src/libraries/System.Net.Quic/src/Resources/Strings.resx b/src/libraries/System.Net.Quic/src/Resources/Strings.resx index 7c032ca720035cfbdddfb81190c2b5ce18956522..2b741679317211c8656a948b492ae72e97739171 100644 --- a/src/libraries/System.Net.Quic/src/Resources/Strings.resx +++ b/src/libraries/System.Net.Quic/src/Resources/Strings.resx @@ -129,8 +129,11 @@ Reading is not allowed on stream. - - Sending has already been aborted on the stream + + Writing has been aborted on the stream. + + + Reading has been aborted on the stream. Stream aborted by peer ({0}). @@ -148,7 +151,7 @@ Timeout can only be set to 'System.Threading.Timeout.Infinite' or a value > 0. - Connection timed out. + Connection timed out waiting for a response from the peer. '{0}' is not supported by System.Net.Quic. @@ -162,6 +165,9 @@ Connection is not connected. + + An internal error has occured. {0} + The application protocol list is invalid. @@ -171,8 +177,39 @@ CipherSuitePolicy must specify at least one cipher supported by QUIC. + + The local address is already in use. + + + The server is currnetly unreachable. + + + The server refused the connection. + + + A QUIC protocol error was encountered + + + A version negotiation error was encountered. + + + Application layer protocol negotiation error was encountered. + + + The connection timed out from inactivity. + + + Binding to socket failed, likely caused by a family mismatch between local and remote address. + + + Authentication failed. {0} + + - This method may not be called when another {0} operation is pending. + This method may not be called when another {0} operation is pending. + + + Authentication failed because the remote party sent a TLS alert: '{0}'. diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs index c63f545c9783a5b0701ecd868c84e732a77d73d3..5cf30951913a9c396d551c34eea8873ef09d631b 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs @@ -45,7 +45,7 @@ private MsQuicApi(QUIC_API_TABLE* apiTable) }; QUIC_HANDLE* handle; - ThrowIfFailure(ApiTable->RegistrationOpen(&cfg, &handle), "RegistrationOpen failed"); + ThrowHelper.ThrowIfMsQuicError(ApiTable->RegistrationOpen(&cfg, &handle), "RegistrationOpen failed"); Registration = new MsQuicSafeHandle(handle, apiTable->RegistrationClose, SafeHandleType.Registration); } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs index 67fdbabf4e449d0bfcbbec33c3287c98e7e37d89..00cc40d71b4cd83b9f9d37f53713d1b73251c9e8 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs @@ -19,7 +19,7 @@ internal static unsafe IPEndPoint GetIPEndPointParam(MsQuicApi api, MsQuicSafeHa fixed (byte* paddress = &MemoryMarshal.GetReference(address)) { - ThrowIfFailure(api.ApiTable->GetParam( + ThrowHelper.ThrowIfMsQuicError(api.ApiTable->GetParam( nativeObject.QuicHandle, param, &valueLen, @@ -42,7 +42,7 @@ internal static unsafe void SetIPEndPointParam(MsQuicApi api, MsQuicSafeHandle n fixed (byte* paddress = &MemoryMarshal.GetReference(address)) { - ThrowIfFailure(api.ApiTable->SetParam( + ThrowHelper.ThrowIfMsQuicError(api.ApiTable->SetParam( nativeObject.QuicHandle, param, (uint)address.Length, @@ -55,7 +55,7 @@ internal static unsafe ushort GetUShortParam(MsQuicApi api, MsQuicSafeHandle nat ushort value; uint valueLen = (uint)sizeof(ushort); - ThrowIfFailure(api.ApiTable->GetParam( + ThrowHelper.ThrowIfMsQuicError(api.ApiTable->GetParam( nativeObject.QuicHandle, param, &valueLen, @@ -67,7 +67,7 @@ internal static unsafe ushort GetUShortParam(MsQuicApi api, MsQuicSafeHandle nat internal static unsafe void SetUShortParam(MsQuicApi api, MsQuicSafeHandle nativeObject, uint param, ushort value) { - ThrowIfFailure(api.ApiTable->SetParam( + ThrowHelper.ThrowIfMsQuicError(api.ApiTable->SetParam( nativeObject.QuicHandle, param, sizeof(ushort), @@ -79,7 +79,7 @@ internal static unsafe ulong GetULongParam(MsQuicApi api, MsQuicSafeHandle nativ ulong value; uint valueLen = (uint)sizeof(ulong); - ThrowIfFailure(api.ApiTable->GetParam( + ThrowHelper.ThrowIfMsQuicError(api.ApiTable->GetParam( nativeObject.QuicHandle, param, &valueLen, @@ -91,7 +91,7 @@ internal static unsafe ulong GetULongParam(MsQuicApi api, MsQuicSafeHandle nativ internal static unsafe void SetULongParam(MsQuicApi api, MsQuicSafeHandle nativeObject, uint param, ulong value) { - ThrowIfFailure(api.ApiTable->SetParam( + ThrowHelper.ThrowIfMsQuicError(api.ApiTable->SetParam( nativeObject.QuicHandle, param, sizeof(ulong), diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicException.cs deleted file mode 100644 index 2dd3f114dd3d7b0f362f0c6a76f928e55075a0a4..0000000000000000000000000000000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/MsQuicException.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Net.Quic; -using System.Net.Sockets; -using static Microsoft.Quic.MsQuic; - -namespace Microsoft.Quic -{ - internal sealed class MsQuicException : QuicException - { - public int Status { get; } - - public MsQuicException(int status, string? message = null, Exception? innerException = null) - : base($"{(message ?? nameof(MsQuicException))}: {GetErrorCodeForStatus(status)}", innerException, MapMsQuicStatusToHResult(status)) - { - Status = status; - } - - public static string GetErrorCodeForStatus(int status) - { - if (status == MsQuic.QUIC_STATUS_SUCCESS) return "QUIC_STATUS_SUCCESS"; - else if (status == MsQuic.QUIC_STATUS_PENDING) return "QUIC_STATUS_PENDING"; - else if (status == MsQuic.QUIC_STATUS_CONTINUE) return "QUIC_STATUS_CONTINUE"; - else if (status == MsQuic.QUIC_STATUS_OUT_OF_MEMORY) return "QUIC_STATUS_OUT_OF_MEMORY"; - else if (status == MsQuic.QUIC_STATUS_INVALID_PARAMETER) return "QUIC_STATUS_INVALID_PARAMETER"; - else if (status == MsQuic.QUIC_STATUS_INVALID_STATE) return "QUIC_STATUS_INVALID_STATE"; - else if (status == MsQuic.QUIC_STATUS_NOT_SUPPORTED) return "QUIC_STATUS_NOT_SUPPORTED"; - else if (status == MsQuic.QUIC_STATUS_NOT_FOUND) return "QUIC_STATUS_NOT_FOUND"; - else if (status == MsQuic.QUIC_STATUS_BUFFER_TOO_SMALL) return "QUIC_STATUS_BUFFER_TOO_SMALL"; - else if (status == MsQuic.QUIC_STATUS_HANDSHAKE_FAILURE) return "QUIC_STATUS_HANDSHAKE_FAILURE"; - else if (status == MsQuic.QUIC_STATUS_ABORTED) return "QUIC_STATUS_ABORTED"; - else if (status == MsQuic.QUIC_STATUS_ADDRESS_IN_USE) return "QUIC_STATUS_ADDRESS_IN_USE"; - else if (status == MsQuic.QUIC_STATUS_CONNECTION_TIMEOUT) return "QUIC_STATUS_CONNECTION_TIMEOUT"; - else if (status == MsQuic.QUIC_STATUS_CONNECTION_IDLE) return "QUIC_STATUS_CONNECTION_IDLE"; - else if (status == MsQuic.QUIC_STATUS_UNREACHABLE) return "QUIC_STATUS_UNREACHABLE"; - else if (status == MsQuic.QUIC_STATUS_INTERNAL_ERROR) return "QUIC_STATUS_INTERNAL_ERROR"; - else if (status == MsQuic.QUIC_STATUS_CONNECTION_REFUSED) return "QUIC_STATUS_CONNECTION_REFUSED"; - else if (status == MsQuic.QUIC_STATUS_PROTOCOL_ERROR) return "QUIC_STATUS_PROTOCOL_ERROR"; - else if (status == MsQuic.QUIC_STATUS_VER_NEG_ERROR) return "QUIC_STATUS_VER_NEG_ERROR"; - else if (status == MsQuic.QUIC_STATUS_TLS_ERROR) return "QUIC_STATUS_TLS_ERROR"; - else if (status == MsQuic.QUIC_STATUS_USER_CANCELED) return "QUIC_STATUS_USER_CANCELED"; - else if (status == MsQuic.QUIC_STATUS_ALPN_NEG_FAILURE) return "QUIC_STATUS_ALPN_NEG_FAILURE"; - else if (status == MsQuic.QUIC_STATUS_STREAM_LIMIT_REACHED) return "QUIC_STATUS_STREAM_LIMIT_REACHED"; - else if (status == MsQuic.QUIC_STATUS_CLOSE_NOTIFY) return "QUIC_STATUS_CLOSE_NOTIFY"; - else if (status == MsQuic.QUIC_STATUS_BAD_CERTIFICATE) return "QUIC_STATUS_BAD_CERTIFICATE"; - else if (status == MsQuic.QUIC_STATUS_UNSUPPORTED_CERTIFICATE) return "QUIC_STATUS_UNSUPPORTED_CERTIFICATE"; - else if (status == MsQuic.QUIC_STATUS_REVOKED_CERTIFICATE) return "QUIC_STATUS_REVOKED_CERTIFICATE"; - else if (status == MsQuic.QUIC_STATUS_EXPIRED_CERTIFICATE) return "QUIC_STATUS_EXPIRED_CERTIFICATE"; - else if (status == MsQuic.QUIC_STATUS_UNKNOWN_CERTIFICATE) return "QUIC_STATUS_UNKNOWN_CERTIFICATE"; - else if (status == MsQuic.QUIC_STATUS_CERT_EXPIRED) return "QUIC_STATUS_CERT_EXPIRED"; - else if (status == MsQuic.QUIC_STATUS_CERT_UNTRUSTED_ROOT) return "QUIC_STATUS_CERT_UNTRUSTED_ROOT"; - else return $"Unknown status '{status}'"; - } - - public static int MapMsQuicStatusToHResult(int status) - { - if (status == QUIC_STATUS_CONNECTION_REFUSED) return (int)SocketError.ConnectionRefused; // 0x8007274D - WSAECONNREFUSED - else if (status == QUIC_STATUS_CONNECTION_TIMEOUT) return (int)SocketError.TimedOut; // 0x8007274C - WSAETIMEDOUT - else if (status == QUIC_STATUS_UNREACHABLE) return (int)SocketError.HostUnreachable; - else return 0; - } - } -} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs index 00529322c0d8a53a170715fa9067e63464b12fd5..fb0f9fc11ffec65150a37f786f1c1c6fafe55268 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/SafeMsQuicConfigurationHandle.cs @@ -167,7 +167,7 @@ private static unsafe SafeMsQuicConfigurationHandle Create(QuicConnectionOptions QUIC_HANDLE* handle; using var msquicBuffers = new MsQuicBuffers(); msquicBuffers.Initialize(alpnProtocols, alpnProtocol => alpnProtocol.Protocol); - ThrowIfFailure(MsQuicApi.Api.ApiTable->ConfigurationOpen( + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConfigurationOpen( MsQuicApi.Api.Registration.QuicHandle, msquicBuffers.Buffers, (uint)alpnProtocols.Count, @@ -247,11 +247,11 @@ private static unsafe SafeMsQuicConfigurationHandle Create(QuicConnectionOptions #if TARGET_WINDOWS if ((Interop.SECURITY_STATUS)status == Interop.SECURITY_STATUS.AlgorithmMismatch && (isServer ? MsQuicApi.Tls13ServerMayBeDisabled : MsQuicApi.Tls13ClientMayBeDisabled)) { - throw new MsQuicException(status, SR.net_quic_tls_version_notsupported); + throw new PlatformNotSupportedException(SR.net_quic_tls_version_notsupported); } #endif - ThrowIfFailure(status, "ConfigurationLoadCredential failed"); + ThrowHelper.ThrowIfMsQuicError(status, "ConfigurationLoadCredential failed"); } catch { diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic.cs index 489c80f62d428841e7a470378040dbbea2b09c66..81f62f081a6880e6d443b7d592f39d947aa42f37 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic.cs @@ -7,7 +7,6 @@ using System; using System.Diagnostics; -using System.Net.Quic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -26,28 +25,6 @@ internal unsafe partial struct QUIC_BUFFER internal partial class MsQuic { - public static unsafe QUIC_API_TABLE* Open() - { - QUIC_API_TABLE* ApiTable; - int Status = MsQuicOpenVersion(2, (void**)&ApiTable); - ThrowIfFailure(Status); - return ApiTable; - } - - public static unsafe void Close(QUIC_API_TABLE* ApiTable) - { - MsQuicClose(ApiTable); - } - - public static void ThrowIfFailure(int status, string? message = null) - { - if (StatusFailed(status)) - { - // TODO make custom exception, and maybe throw helpers - throw new MsQuicException(status, message); - } - } - public static bool StatusSucceeded(int status) { if (OperatingSystem.IsWindows()) @@ -84,6 +61,7 @@ public static bool StatusFailed(int status) public static int QUIC_STATUS_HANDSHAKE_FAILURE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_HANDSHAKE_FAILURE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_HANDSHAKE_FAILURE : MsQuic_Linux.QUIC_STATUS_HANDSHAKE_FAILURE; public static int QUIC_STATUS_ABORTED => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_ABORTED : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_ABORTED : MsQuic_Linux.QUIC_STATUS_ABORTED; public static int QUIC_STATUS_ADDRESS_IN_USE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_ADDRESS_IN_USE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_ADDRESS_IN_USE : MsQuic_Linux.QUIC_STATUS_ADDRESS_IN_USE; + public static int QUIC_STATUS_INVALID_ADDRESS => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_INVALID_ADDRESS : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_INVALID_ADDRESS : MsQuic_Linux.QUIC_STATUS_INVALID_ADDRESS; public static int QUIC_STATUS_CONNECTION_TIMEOUT => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_CONNECTION_TIMEOUT : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_CONNECTION_TIMEOUT : MsQuic_Linux.QUIC_STATUS_CONNECTION_TIMEOUT; public static int QUIC_STATUS_CONNECTION_IDLE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_CONNECTION_IDLE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_CONNECTION_IDLE : MsQuic_Linux.QUIC_STATUS_CONNECTION_IDLE; public static int QUIC_STATUS_UNREACHABLE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_UNREACHABLE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_UNREACHABLE : MsQuic_Linux.QUIC_STATUS_UNREACHABLE; @@ -101,8 +79,10 @@ public static bool StatusFailed(int status) public static int QUIC_STATUS_REVOKED_CERTIFICATE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_REVOKED_CERTIFICATE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_REVOKED_CERTIFICATE : MsQuic_Linux.QUIC_STATUS_REVOKED_CERTIFICATE; public static int QUIC_STATUS_EXPIRED_CERTIFICATE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_EXPIRED_CERTIFICATE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_EXPIRED_CERTIFICATE : MsQuic_Linux.QUIC_STATUS_EXPIRED_CERTIFICATE; public static int QUIC_STATUS_UNKNOWN_CERTIFICATE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_UNKNOWN_CERTIFICATE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_UNKNOWN_CERTIFICATE : MsQuic_Linux.QUIC_STATUS_UNKNOWN_CERTIFICATE; + public static int QUIC_STATUS_REQUIRED_CERTIFICATE => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_REQUIRED_CERTIFICATE : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_REQUIRED_CERTIFICATE : MsQuic_Linux.QUIC_STATUS_REQUIRED_CERTIFICATE; public static int QUIC_STATUS_CERT_EXPIRED => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_CERT_EXPIRED : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_CERT_EXPIRED : MsQuic_Linux.QUIC_STATUS_CERT_EXPIRED; public static int QUIC_STATUS_CERT_UNTRUSTED_ROOT => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_CERT_UNTRUSTED_ROOT : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_CERT_UNTRUSTED_ROOT : MsQuic_Linux.QUIC_STATUS_CERT_UNTRUSTED_ROOT; + public static int QUIC_STATUS_CERT_NO_CERT => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_STATUS_CERT_NO_CERT : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_STATUS_CERT_NO_CERT : MsQuic_Linux.QUIC_STATUS_CERT_NO_CERT; public static int QUIC_ADDRESS_FAMILY_UNSPEC => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_ADDRESS_FAMILY_UNSPEC : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_ADDRESS_FAMILY_UNSPEC : MsQuic_Linux.QUIC_ADDRESS_FAMILY_UNSPEC; public static int QUIC_ADDRESS_FAMILY_INET => OperatingSystem.IsWindows() ? MsQuic_Windows.QUIC_ADDRESS_FAMILY_INET : (OperatingSystem.IsLinux() || OperatingSystem.IsAndroid()) ? MsQuic_Linux.QUIC_ADDRESS_FAMILY_INET : MsQuic_Linux.QUIC_ADDRESS_FAMILY_INET; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated.cs index b0351c909b98abfbbf90813b3f5df5ee4a523588..d74bd9929cd3cfc81088be1fbfd321cf0438a1c8 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated.cs @@ -22,6 +22,12 @@ internal partial struct QUIC_HANDLE { } + internal enum QUIC_TLS_PROVIDER + { + SCHANNEL = 0x0000, + OPENSSL = 0x0001, + } + internal enum QUIC_EXECUTION_PROFILE { LOW_LATENCY, @@ -816,13 +822,13 @@ internal enum QUIC_PERFORMANCE_COUNTERS internal unsafe partial struct QUIC_VERSION_SETTINGS { - [NativeTypeName("uint32_t *")] + [NativeTypeName("const uint32_t *")] internal uint* AcceptableVersions; - [NativeTypeName("uint32_t *")] + [NativeTypeName("const uint32_t *")] internal uint* OfferedVersions; - [NativeTypeName("uint32_t *")] + [NativeTypeName("const uint32_t *")] internal uint* FullyDeployedVersions; [NativeTypeName("uint32_t")] @@ -2029,6 +2035,9 @@ internal partial struct _SHUTDOWN_INITIATED_BY_TRANSPORT_e__Struct { [NativeTypeName("HRESULT")] internal int Status; + + [NativeTypeName("QUIC_UINT62")] + internal ulong ErrorCode; } internal partial struct _SHUTDOWN_INITIATED_BY_PEER_e__Struct @@ -2401,19 +2410,36 @@ internal byte AppCloseInProgress } } - [NativeTypeName("BOOLEAN : 7")] + [NativeTypeName("BOOLEAN : 1")] + internal byte ConnectionShutdownByPeer + { + get + { + return (byte)((_bitfield >> 1) & 0x1u); + } + + set + { + _bitfield = (byte)((_bitfield & ~(0x1u << 1)) | ((value & 0x1u) << 1)); + } + } + + [NativeTypeName("BOOLEAN : 6")] internal byte RESERVED { get { - return (byte)((_bitfield >> 1) & 0x7Fu); + return (byte)((_bitfield >> 2) & 0x3Fu); } set { - _bitfield = (byte)((_bitfield & ~(0x7Fu << 1)) | ((value & 0x7Fu) << 1)); + _bitfield = (byte)((_bitfield & ~(0x3Fu << 2)) | ((value & 0x3Fu) << 2)); } } + + [NativeTypeName("QUIC_UINT62")] + internal ulong ConnectionErrorCode; } internal partial struct _IDEAL_SEND_BUFFER_SIZE_e__Struct @@ -2592,6 +2618,9 @@ internal static unsafe partial class MsQuic [NativeTypeName("#define QUIC_PARAM_GLOBAL_DATAPATH_PROCESSORS 0x01000009")] internal const uint QUIC_PARAM_GLOBAL_DATAPATH_PROCESSORS = 0x01000009; + [NativeTypeName("#define QUIC_PARAM_GLOBAL_TLS_PROVIDER 0x0100000A")] + internal const uint QUIC_PARAM_GLOBAL_TLS_PROVIDER = 0x0100000A; + [NativeTypeName("#define QUIC_PARAM_CONFIGURATION_SETTINGS 0x03000000")] internal const uint QUIC_PARAM_CONFIGURATION_SETTINGS = 0x03000000; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_linux.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_linux.cs index 67e3967b4fb0ec7e975c396f2ce8eedbf39c049f..880ed49b887dee857bbb4e8bf26fd5d39fb3f3e7 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_linux.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_linux.cs @@ -45,6 +45,9 @@ internal static unsafe partial class MsQuic_Linux [NativeTypeName("#define QUIC_STATUS_ADDRESS_IN_USE ((QUIC_STATUS)EADDRINUSE)")] public const int QUIC_STATUS_ADDRESS_IN_USE = ((int)(98)); + [NativeTypeName("#define QUIC_STATUS_INVALID_ADDRESS ((QUIC_STATUS)EAFNOSUPPORT)")] + public const int QUIC_STATUS_INVALID_ADDRESS = ((int)(97)); + [NativeTypeName("#define QUIC_STATUS_CONNECTION_TIMEOUT ((QUIC_STATUS)ETIMEDOUT)")] public const int QUIC_STATUS_CONNECTION_TIMEOUT = ((int)(110)); @@ -96,12 +99,18 @@ internal static unsafe partial class MsQuic_Linux [NativeTypeName("#define QUIC_STATUS_UNKNOWN_CERTIFICATE QUIC_STATUS_TLS_ALERT(46)")] public const int QUIC_STATUS_UNKNOWN_CERTIFICATE = ((int)(0xff & 46) + 256 + 200000000); + [NativeTypeName("#define QUIC_STATUS_REQUIRED_CERTIFICATE QUIC_STATUS_TLS_ALERT(116)")] + public const int QUIC_STATUS_REQUIRED_CERTIFICATE = ((int)(0xff & 116) + 256 + 200000000); + [NativeTypeName("#define QUIC_STATUS_CERT_EXPIRED QUIC_STATUS_CERT_ERROR(1)")] public const int QUIC_STATUS_CERT_EXPIRED = ((int)(1) + 512 + 200000000); [NativeTypeName("#define QUIC_STATUS_CERT_UNTRUSTED_ROOT QUIC_STATUS_CERT_ERROR(2)")] public const int QUIC_STATUS_CERT_UNTRUSTED_ROOT = ((int)(2) + 512 + 200000000); + [NativeTypeName("#define QUIC_STATUS_CERT_NO_CERT QUIC_STATUS_CERT_ERROR(3)")] + public const int QUIC_STATUS_CERT_NO_CERT = ((int)(3) + 512 + 200000000); + public const int QUIC_ADDRESS_FAMILY_UNSPEC = 0; public const int QUIC_ADDRESS_FAMILY_INET = 2; public const int QUIC_ADDRESS_FAMILY_INET6 = 10; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_macos.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_macos.cs index 00a3ad792624f27cbee30f3230b28e3732fd1546..f3fa6b0de89248e953b8999df4cc9cceb7058474 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_macos.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_macos.cs @@ -45,6 +45,9 @@ internal static unsafe partial class MsQuic_MacOS [NativeTypeName("#define QUIC_STATUS_ADDRESS_IN_USE ((QUIC_STATUS)EADDRINUSE)")] public const int QUIC_STATUS_ADDRESS_IN_USE = ((int)(48)); + [NativeTypeName("#define QUIC_STATUS_INVALID_ADDRESS ((QUIC_STATUS)EAFNOSUPPORT)")] + public const int QUIC_STATUS_INVALID_ADDRESS = ((int)(47)); + [NativeTypeName("#define QUIC_STATUS_CONNECTION_TIMEOUT ((QUIC_STATUS)ETIMEDOUT)")] public const int QUIC_STATUS_CONNECTION_TIMEOUT = ((int)(60)); @@ -96,12 +99,18 @@ internal static unsafe partial class MsQuic_MacOS [NativeTypeName("#define QUIC_STATUS_UNKNOWN_CERTIFICATE QUIC_STATUS_TLS_ALERT(46)")] public const int QUIC_STATUS_UNKNOWN_CERTIFICATE = ((int)(0xff & 46) + 256 + 200000000); + [NativeTypeName("#define QUIC_STATUS_REQUIRED_CERTIFICATE QUIC_STATUS_TLS_ALERT(116)")] + public const int QUIC_STATUS_REQUIRED_CERTIFICATE = ((int)(0xff & 116) + 256 + 200000000); + [NativeTypeName("#define QUIC_STATUS_CERT_EXPIRED QUIC_STATUS_CERT_ERROR(1)")] public const int QUIC_STATUS_CERT_EXPIRED = ((int)(1) + 512 + 200000000); [NativeTypeName("#define QUIC_STATUS_CERT_UNTRUSTED_ROOT QUIC_STATUS_CERT_ERROR(2)")] public const int QUIC_STATUS_CERT_UNTRUSTED_ROOT = ((int)(2) + 512 + 200000000); + [NativeTypeName("#define QUIC_STATUS_CERT_NO_CERT QUIC_STATUS_CERT_ERROR(3)")] + public const int QUIC_STATUS_CERT_NO_CERT = ((int)(3) + 512 + 200000000); + public const int QUIC_ADDRESS_FAMILY_UNSPEC = 0; public const int QUIC_ADDRESS_FAMILY_INET = 2; public const int QUIC_ADDRESS_FAMILY_INET6 = 30; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_windows.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_windows.cs index ee8cf6bf74ccbaa6460aefab28d7c76f729a8701..ea4f755da5d7cfe009eba671a0fdbe1b7ea8f841 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_windows.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Interop/msquic_generated_windows.cs @@ -45,6 +45,9 @@ internal static partial class MsQuic_Windows [NativeTypeName("#define QUIC_STATUS_ADDRESS_IN_USE QUIC_STATUS_HRESULT_FROM_WIN32(WSAEADDRINUSE)")] public const int QUIC_STATUS_ADDRESS_IN_USE = unchecked((int)(10048) <= 0 ? ((int)(10048)) : ((int)(((10048) & 0x0000FFFF) | (7 << 16) | 0x80000000))); + [NativeTypeName("#define QUIC_STATUS_INVALID_ADDRESS QUIC_STATUS_HRESULT_FROM_WIN32(WSAEADDRNOTAVAIL)")] + public const int QUIC_STATUS_INVALID_ADDRESS = unchecked((int)(10049) <= 0 ? ((int)(10049)) : ((int)(((10049) & 0x0000FFFF) | (7 << 16) | 0x80000000))); + [NativeTypeName("#define QUIC_STATUS_CONNECTION_TIMEOUT ERROR_QUIC_CONNECTION_TIMEOUT")] public const int QUIC_STATUS_CONNECTION_TIMEOUT = unchecked((int)(0x80410006)); @@ -96,12 +99,18 @@ internal static partial class MsQuic_Windows [NativeTypeName("#define QUIC_STATUS_UNKNOWN_CERTIFICATE QUIC_STATUS_TLS_ALERT(46)")] public const int QUIC_STATUS_UNKNOWN_CERTIFICATE = unchecked(((int)(0x80410100)) | (0xff & 46)); + [NativeTypeName("#define QUIC_STATUS_REQUIRED_CERTIFICATE QUIC_STATUS_TLS_ALERT(116)")] + public const int QUIC_STATUS_REQUIRED_CERTIFICATE = unchecked(((int)(0x80410100)) | (0xff & 116)); + [NativeTypeName("#define QUIC_STATUS_CERT_EXPIRED CERT_E_EXPIRED")] public const int QUIC_STATUS_CERT_EXPIRED = unchecked((int)(0x800B0101)); [NativeTypeName("#define QUIC_STATUS_CERT_UNTRUSTED_ROOT CERT_E_UNTRUSTEDROOT")] public const int QUIC_STATUS_CERT_UNTRUSTED_ROOT = unchecked((int)(0x800B0109)); + [NativeTypeName("#define QUIC_STATUS_CERT_NO_CERT SEC_E_NO_CREDENTIALS")] + public const int QUIC_STATUS_CERT_NO_CERT = unchecked((int)(0x8009030E)); + public const int QUIC_ADDRESS_FAMILY_UNSPEC = 0; public const int QUIC_ADDRESS_FAMILY_INET = 2; public const int QUIC_ADDRESS_FAMILY_INET6 = 23; diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index f0f9abdb4793f8191cf59bd1495ad6ec9874b779..759c5e1aae71a829d77b8441bbb1999d4be2776c 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -173,7 +173,7 @@ public unsafe MsQuicConnection(QuicClientConnectionOptions options) { QUIC_HANDLE* handle; Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)"); - ThrowIfFailure(MsQuicApi.Api.ApiTable->ConnectionOpen( + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionOpen( MsQuicApi.Api.Registration.QuicHandle, &NativeCallback, (void*)GCHandle.ToIntPtr(_state.StateGCHandle), @@ -236,7 +236,7 @@ private static int HandleEventShutdownInitiatedByTransport(State state, ref QUIC state.Connection = null; } - state.ConnectTcs.TrySetException(new MsQuicException(connectionEvent.SHUTDOWN_INITIATED_BY_TRANSPORT.Status, "Connection has been shutdown by transport")); + state.ConnectTcs.TrySetException(ThrowHelper.GetExceptionForMsQuicStatus(connectionEvent.SHUTDOWN_INITIATED_BY_TRANSPORT.Status, "Connection has been shutdown by transport")); } // To throw QuicConnectionAbortedException (instead of QuicOperationAbortedException) out of AcceptStreamAsync() since @@ -545,7 +545,7 @@ internal unsafe ValueTask ConnectAsync(CancellationToken cancellationToken = def try { Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)"); - ThrowIfFailure(MsQuicApi.Api.ApiTable->ConnectionStart( + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionStart( _state.Handle.QuicHandle, _configuration.QuicHandle, af, @@ -584,7 +584,7 @@ internal unsafe ValueTask FinishHandshakeAsync(QuicServerConnectionOptions optio _state.RevocationMode = options.ServerAuthenticationOptions.CertificateRevocationCheckMode; _state.RemoteCertificateValidationCallback = options.ServerAuthenticationOptions.RemoteCertificateValidationCallback; _configuration = SafeMsQuicConfigurationHandle.Create(options, options.ServerAuthenticationOptions, targetHost); - ThrowIfFailure(MsQuicApi.Api.ApiTable->ConnectionSetConfiguration( + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ConnectionSetConfiguration( _state.Handle.QuicHandle, _configuration.QuicHandle)); } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs index ef73dda06d0b3c1aac93b5398363235e3a8a716b..485ebc7d59d6bc5c8232f4617cadd8f7560bb824 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicStream.cs @@ -182,7 +182,7 @@ internal unsafe MsQuicStream(MsQuicConnection.State connectionState, QUIC_STREAM throw ThrowHelper.GetConnectionAbortedException(connectionState.AbortErrorCode); } - ThrowIfFailure(status, "Failed to open stream to peer"); + ThrowHelper.ThrowIfMsQuicError(status, "Failed to open stream to peer"); _state.Handle = new SafeMsQuicStreamHandle(handle); } catch @@ -307,11 +307,11 @@ private async ValueTask WriteAsync(Action stateSetup, T if (_state.SendErrorCode != -1) { // aborted by peer - throw new QuicStreamAbortedException(_state.SendErrorCode); + throw ThrowHelper.GetStreamAbortedException(_state.SendErrorCode); } // aborted locally - throw new QuicOperationAbortedException(SR.net_quic_sending_aborted); + throw ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted); } // if token was already cancelled, this would execute synchronously @@ -345,11 +345,11 @@ private async ValueTask WriteAsync(Action stateSetup, T if (_state.SendErrorCode != -1) { // aborted by peer - throw new QuicStreamAbortedException(_state.SendErrorCode); + throw ThrowHelper.GetStreamAbortedException(_state.SendErrorCode); } // aborted locally - throw new QuicOperationAbortedException(SR.net_quic_sending_aborted); + throw ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted); } if (_state.SendState == SendState.ConnectionClosed) { @@ -413,9 +413,13 @@ private unsafe ValueTask WriteAsyncCore(Action stateSet if (status == QUIC_STATUS_ABORTED) { + if (_state.SendErrorCode != -1) + { + throw ThrowHelper.GetStreamAbortedException(_state.SendErrorCode); + } throw ThrowHelper.GetConnectionAbortedException(_state.ConnectionState.AbortErrorCode); } - ThrowIfFailure(status, "Could not send data to peer."); + ThrowHelper.ThrowIfMsQuicError(status, "Could not send data to peer."); } return _state.SendResettableCompletionSource.GetTypelessValueTask(); @@ -618,7 +622,7 @@ internal void AbortRead(long errorCode) if (shouldComplete) { _state.ReceiveResettableCompletionSource.CompleteException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException("Read was aborted"))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException(SR.net_quic_reading_aborted))); } StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_RECEIVE, errorCode); @@ -659,13 +663,13 @@ internal void AbortWrite(long errorCode) if (shouldComplete) { _state.ShutdownWriteCompletionSource.SetException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException("Write was aborted."))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted))); } if (shouldCompleteSends) { _state.SendResettableCompletionSource.CompleteException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException("Write was aborted."))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException(SR.net_quic_writing_aborted))); } StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS.ABORT_SEND, errorCode); @@ -674,7 +678,7 @@ internal void AbortWrite(long errorCode) private unsafe void StartShutdown(QUIC_STREAM_SHUTDOWN_FLAGS flags, long errorCode) { Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)"); - ThrowIfFailure(MsQuicApi.Api.ApiTable->StreamShutdown( + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamShutdown( _state.Handle.QuicHandle, flags, (uint)errorCode), "StreamShutdown failed"); @@ -897,7 +901,7 @@ private void Dispose(bool disposing) if (completeRead) { _state.ReceiveResettableCompletionSource.CompleteException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException("Read was canceled"))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException())); } @@ -914,7 +918,7 @@ private void Dispose(bool disposing) private unsafe void EnableReceive() { Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)"); - ThrowIfFailure(MsQuicApi.Api.ApiTable->StreamReceiveSetEnabled(_state.Handle.QuicHandle, 1), "StreamReceiveSetEnabled failed"); + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->StreamReceiveSetEnabled(_state.Handle.QuicHandle, 1), "StreamReceiveSetEnabled failed"); } /// @@ -1136,13 +1140,13 @@ private static int HandleEventPeerRecvAborted(State state, ref QUIC_STREAM_EVENT if (shouldSendComplete) { state.SendResettableCompletionSource.CompleteException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicStreamAbortedException(state.SendErrorCode))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetStreamAbortedException(state.SendErrorCode))); } if (shouldShutdownWriteComplete) { state.ShutdownWriteCompletionSource.SetException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicStreamAbortedException(state.SendErrorCode))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetStreamAbortedException(state.SendErrorCode))); } return QUIC_STATUS_SUCCESS; @@ -1187,7 +1191,7 @@ private static int HandleEventStartComplete(State state, ref QUIC_STREAM_EVENT s // TODO: Should we throw QuicOperationAbortedException when status is InvalidState? // [ActiveIssue("https://github.com/dotnet/runtime/issues/55619")] state.StartCompletionSource.TrySetException( - ExceptionDispatchInfo.SetCurrentStackTrace(new MsQuicException(status, "StreamStart failed"))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetExceptionForMsQuicStatus(status, "StreamStart failed"))); } } @@ -1271,7 +1275,7 @@ private static int HandleEventShutdownComplete(State state, ref QUIC_STREAM_EVEN else { state.ReceiveResettableCompletionSource.CompleteException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException($"Stream start failed"))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException($"Stream start failed"))); } } @@ -1284,7 +1288,7 @@ private static int HandleEventShutdownComplete(State state, ref QUIC_STREAM_EVEN else { state.ShutdownWriteCompletionSource.SetException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException($"Stream start failed"))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException($"Stream start failed"))); } } @@ -1325,7 +1329,7 @@ private static int HandleEventPeerSendAborted(State state, ref QUIC_STREAM_EVENT if (shouldComplete) { state.ReceiveResettableCompletionSource.CompleteException( - ExceptionDispatchInfo.SetCurrentStackTrace(new QuicStreamAbortedException(state.ReadErrorCode))); + ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetStreamAbortedException(state.ReadErrorCode))); } return QUIC_STATUS_SUCCESS; @@ -1543,7 +1547,7 @@ internal async ValueTask StartAsync(CancellationToken cancellationToken) if (!StatusSucceeded(status)) { - Exception exception = new MsQuicException(status, "Could not start stream"); + Exception exception = ThrowHelper.GetExceptionForMsQuicStatus(status, "Could not start stream"); _state.StartCompletionSource.TrySetException(ExceptionDispatchInfo.SetCurrentStackTrace(exception)); throw exception; } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/ThrowHelper.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/ThrowHelper.cs deleted file mode 100644 index b64cda77b355dd4267cbea6a5328d302e1ece920..0000000000000000000000000000000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/ThrowHelper.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Quic.Implementations.MsQuic -{ - internal static class ThrowHelper - { - internal static Exception GetConnectionAbortedException(long errorCode) - { - return errorCode switch - { - -1 => new QuicOperationAbortedException(), // Shutdown initiated by us. - long err => new QuicConnectionAbortedException(err) // Shutdown initiated by peer. - }; - } - - internal static Exception GetStreamAbortedException(long errorCode) - { - return errorCode switch - { - -1 => new QuicOperationAbortedException(), // Shutdown initiated by us. - long err => new QuicStreamAbortedException(err) // Shutdown initiated by peer. - }; - } - } -} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ThrowHelper.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ThrowHelper.cs new file mode 100644 index 0000000000000000000000000000000000000000..cdfebdb725457c06bfd8d8e95d1ab0426edcf388 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/ThrowHelper.cs @@ -0,0 +1,155 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Quic; +using System.Security.Authentication; +using static Microsoft.Quic.MsQuic; + +namespace System.Net.Quic.Implementations.MsQuic +{ + internal static class ThrowHelper + { + internal static QuicException GetConnectionAbortedException(long errorCode) + { + return errorCode switch + { + -1 => GetOperationAbortedException(), // Shutdown initiated by us. + long err => new QuicException(QuicError.ConnectionAborted, err, SR.Format(SR.net_quic_connectionaborted, err)) // Shutdown initiated by peer. + }; + } + + internal static QuicException GetStreamAbortedException(long errorCode) + { + return errorCode switch + { + -1 => GetOperationAbortedException(), // Shutdown initiated by us. + long err => new QuicException(QuicError.StreamAborted, err, SR.Format(SR.net_quic_streamaborted, err)) // Shutdown initiated by peer. + }; + } + + internal static QuicException GetOperationAbortedException(string? message = null) + { + return new QuicException(QuicError.OperationAborted, null, message ?? SR.net_quic_operationaborted); + } + + internal static Exception GetExceptionForMsQuicStatus(int status, string? message = null) + { + Exception ex = GetExceptionInternal(status, message); + if (status != 0) + { + // Include the raw MsQuic status in the HResult property for better diagnostics + ex.HResult = status; + } + + return ex; + + static Exception GetExceptionInternal(int status, string? message) + { + // + // Start by checking for statuses mapped to QuicError enum + // + if (status == QUIC_STATUS_ADDRESS_IN_USE) return new QuicException(QuicError.AddressInUse, null, SR.net_quic_address_in_use); + if (status == QUIC_STATUS_UNREACHABLE) return new QuicException(QuicError.HostUnreachable, null, SR.net_quic_host_unreachable); + if (status == QUIC_STATUS_CONNECTION_REFUSED) return new QuicException(QuicError.ConnectionRefused, null, SR.net_quic_connection_refused); + if (status == QUIC_STATUS_VER_NEG_ERROR) return new QuicException(QuicError.VersionNegotiationError, null, SR.net_quic_ver_neg_error); + if (status == QUIC_STATUS_INVALID_ADDRESS) return new QuicException(QuicError.InvalidAddress, null, SR.net_quic_invalid_address); + if (status == QUIC_STATUS_CONNECTION_IDLE) return new QuicException(QuicError.ConnectionIdle, null, SR.net_quic_connection_idle); + if (status == QUIC_STATUS_PROTOCOL_ERROR) return new QuicException(QuicError.ProtocolError, null, SR.net_quic_protocol_error); + + if (status == QUIC_STATUS_TLS_ERROR || + status == QUIC_STATUS_CERT_EXPIRED || + status == QUIC_STATUS_CERT_UNTRUSTED_ROOT || + status == QUIC_STATUS_CERT_NO_CERT) + { + return new AuthenticationException(SR.Format(SR.net_quic_auth, GetErrorMessageForStatus(status, message))); + } + + // + // Although ALPN negotiation failure is triggered by a TLS Alert, it is mapped differently + // + if (status == QUIC_STATUS_ALPN_NEG_FAILURE) + { + return new AuthenticationException(SR.net_quic_alpn_neg_error); + } + + // + // other TLS Alerts: MsQuic maps TLS alerts by offsetting them by a + // certain value. CloseNotify is the TLS Alert with value 0x00, so + // all TLS Alert codes are mapped to [QUIC_STATUS_CLOSE_NOTIFY, + // QUIC_STATUS_CLOSE_NOTIFY + 255] + // + // Mapped TLS alerts include following statuses: + // - QUIC_STATUS_CLOSE_NOTIFY + // - QUIC_STATUS_BAD_CERTIFICATE + // - QUIC_STATUS_UNSUPPORTED_CERTIFICATE + // - QUIC_STATUS_REVOKED_CERTIFICATE + // - QUIC_STATUS_EXPIRED_CERTIFICATE + // - QUIC_STATUS_UNKNOWN_CERTIFICATE + // - QUIC_STATUS_REQUIRED_CERTIFICATE + // + if ((uint)status >= (uint)QUIC_STATUS_CLOSE_NOTIFY && (uint)status < (uint)QUIC_STATUS_CLOSE_NOTIFY + 256) + { + int alert = status - QUIC_STATUS_CLOSE_NOTIFY; + return new AuthenticationException(SR.Format(SR.net_auth_tls_alert, alert)); + } + + // + // for everything else, use general InternalError + // + return new QuicException(QuicError.InternalError, null, SR.Format(SR.net_quic_internal_error, GetErrorMessageForStatus(status, message))); + } + } + + internal static void ThrowIfMsQuicError(int status, string? message = null) + { + if (StatusFailed(status)) + { + throw GetExceptionForMsQuicStatus(status, message); + } + } + + internal static string GetErrorMessageForStatus(int status, string? message) + { + return (message ?? "Status code") + ": " + GetErrorMessageForStatus(status); + } + + internal static string GetErrorMessageForStatus(int status) + { + if (status == QUIC_STATUS_SUCCESS) return "QUIC_STATUS_SUCCESS"; + else if (status == QUIC_STATUS_PENDING) return "QUIC_STATUS_PENDING"; + else if (status == QUIC_STATUS_CONTINUE) return "QUIC_STATUS_CONTINUE"; + else if (status == QUIC_STATUS_OUT_OF_MEMORY) return "QUIC_STATUS_OUT_OF_MEMORY"; + else if (status == QUIC_STATUS_INVALID_PARAMETER) return "QUIC_STATUS_INVALID_PARAMETER"; + else if (status == QUIC_STATUS_INVALID_STATE) return "QUIC_STATUS_INVALID_STATE"; + else if (status == QUIC_STATUS_NOT_SUPPORTED) return "QUIC_STATUS_NOT_SUPPORTED"; + else if (status == QUIC_STATUS_NOT_FOUND) return "QUIC_STATUS_NOT_FOUND"; + else if (status == QUIC_STATUS_BUFFER_TOO_SMALL) return "QUIC_STATUS_BUFFER_TOO_SMALL"; + else if (status == QUIC_STATUS_HANDSHAKE_FAILURE) return "QUIC_STATUS_HANDSHAKE_FAILURE"; + else if (status == QUIC_STATUS_ABORTED) return "QUIC_STATUS_ABORTED"; + else if (status == QUIC_STATUS_ADDRESS_IN_USE) return "QUIC_STATUS_ADDRESS_IN_USE"; + else if (status == QUIC_STATUS_INVALID_ADDRESS) return "QUIC_STATUS_INVALID_ADDRESS"; + else if (status == QUIC_STATUS_CONNECTION_TIMEOUT) return "QUIC_STATUS_CONNECTION_TIMEOUT"; + else if (status == QUIC_STATUS_CONNECTION_IDLE) return "QUIC_STATUS_CONNECTION_IDLE"; + else if (status == QUIC_STATUS_UNREACHABLE) return "QUIC_STATUS_UNREACHABLE"; + else if (status == QUIC_STATUS_INTERNAL_ERROR) return "QUIC_STATUS_INTERNAL_ERROR"; + else if (status == QUIC_STATUS_CONNECTION_REFUSED) return "QUIC_STATUS_CONNECTION_REFUSED"; + else if (status == QUIC_STATUS_PROTOCOL_ERROR) return "QUIC_STATUS_PROTOCOL_ERROR"; + else if (status == QUIC_STATUS_VER_NEG_ERROR) return "QUIC_STATUS_VER_NEG_ERROR"; + else if (status == QUIC_STATUS_TLS_ERROR) return "QUIC_STATUS_TLS_ERROR"; + else if (status == QUIC_STATUS_USER_CANCELED) return "QUIC_STATUS_USER_CANCELED"; + else if (status == QUIC_STATUS_ALPN_NEG_FAILURE) return "QUIC_STATUS_ALPN_NEG_FAILURE"; + else if (status == QUIC_STATUS_STREAM_LIMIT_REACHED) return "QUIC_STATUS_STREAM_LIMIT_REACHED"; + else if (status == QUIC_STATUS_CLOSE_NOTIFY) return "QUIC_STATUS_CLOSE_NOTIFY"; + else if (status == QUIC_STATUS_BAD_CERTIFICATE) return "QUIC_STATUS_BAD_CERTIFICATE"; + else if (status == QUIC_STATUS_UNSUPPORTED_CERTIFICATE) return "QUIC_STATUS_UNSUPPORTED_CERTIFICATE"; + else if (status == QUIC_STATUS_REVOKED_CERTIFICATE) return "QUIC_STATUS_REVOKED_CERTIFICATE"; + else if (status == QUIC_STATUS_EXPIRED_CERTIFICATE) return "QUIC_STATUS_EXPIRED_CERTIFICATE"; + else if (status == QUIC_STATUS_UNKNOWN_CERTIFICATE) return "QUIC_STATUS_UNKNOWN_CERTIFICATE"; + else if (status == QUIC_STATUS_REQUIRED_CERTIFICATE) return "QUIC_STATUS_REQUIRED_CERTIFICATE"; + else if (status == QUIC_STATUS_CERT_EXPIRED) return "QUIC_STATUS_CERT_EXPIRED"; + else if (status == QUIC_STATUS_CERT_UNTRUSTED_ROOT) return "QUIC_STATUS_CERT_UNTRUSTED_ROOT"; + else if (status == QUIC_STATUS_CERT_NO_CERT) return "QUIC_STATUS_CERT_NO_CERT"; + else return $"Unknown (0x{status:x})"; + } + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnectionAbortedException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnectionAbortedException.cs deleted file mode 100644 index ed65d8c06fbb738409714699c2545155f99db5d7..0000000000000000000000000000000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnectionAbortedException.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Quic -{ - public class QuicConnectionAbortedException : QuicException - { - internal QuicConnectionAbortedException(long errorCode) - : this(SR.Format(SR.net_quic_connectionaborted, errorCode), errorCode) - { - } - - public QuicConnectionAbortedException(string message, long errorCode) - : base (message) - { - ErrorCode = errorCode; - } - - public long ErrorCode { get; } - } -} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicError.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicError.cs new file mode 100644 index 0000000000000000000000000000000000000000..884360b5332c84f76eff5560c489a824e7b39387 --- /dev/null +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicError.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Net.Quic +{ + /// + /// Defines the various error conditions for , and operations. + /// + public enum QuicError + { + /// + /// No error. + /// + Success, + + /// + /// An internal implementation error has occured. + /// + InternalError, + + /// + /// The connection was aborted by the peer. This error is associated with an application-level error code. + /// + ConnectionAborted, + + /// + /// The read or write direction of the stream was aborted by the peer. This error is associated with an application-level error code. + /// + StreamAborted, + + /// + /// The local address is already in use. + /// + AddressInUse, + + /// + /// Binding to socket failed, likely caused by a family mismatch between local and remote address. + /// + InvalidAddress, + + /// + /// The connection timed out waiting for a response from the peer. + /// + ConnectionTimeout, + + /// + /// The server is currently unreachable. + /// + HostUnreachable, + + /// + /// The server refused the connection. + /// + ConnectionRefused, + + /// + /// A version negotiation error was encountered. + /// + VersionNegotiationError, + + /// + /// The connection timed out from inactivity. + /// + ConnectionIdle, + + /// + /// A QUIC protocol error was encountered. + /// + ProtocolError, + + /// + /// The operation has been aborted. + /// + OperationAborted, + } +} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs index 151eb54392f1e61cca5a985041f46c9fce6001d8..40b8d38df25fe5d8ae86e6d1fb690d167f1f0efb 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicException.cs @@ -1,28 +1,36 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; + namespace System.Net.Quic { - public class QuicException : Exception + /// + /// The exception that is thrown when a QUIC error occurs. + /// + public sealed class QuicException : IOException { - public QuicException(string? message) + /// + /// Initializes a new instance of the class. + /// + public QuicException(QuicError error, long? applicationErrorCode, string message) : base(message) { - } - public QuicException(string? message, Exception? innerException) - : base(message, innerException) - { + QuicError = error; + ApplicationErrorCode = applicationErrorCode; } - public QuicException(string? message, Exception? innerException, int result) - : base(message, innerException) - { - // HResult 0 means OK, so do not override the default value set by Exception ctor, - // because in this case we don't have an HResult. - if (result != 0) - { - HResult = result; - } - } + /// + /// Gets the error which is associated with this exception. + /// + public QuicError QuicError { get; } + + /// + /// The application protocol error code associated with the error. + /// + /// + /// This property contains the error code set by the application layer when closing the connection () or closing a read/write direction of a QUIC stream (). Contains null for all other errors. + /// + public long? ApplicationErrorCode { get; } } } diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs index 75cbde31833b12171b74e18a2fd55effd7fe983f..5b895fe4a9396eb4db5b5d0940a8de6ed1ec6997 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicListener.cs @@ -109,7 +109,7 @@ private unsafe QuicListener(QuicListenerOptions options) try { QUIC_HANDLE* handle; - ThrowIfFailure(MsQuicApi.Api.ApiTable->ListenerOpen( + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerOpen( MsQuicApi.Api.Registration.QuicHandle, &NativeCallback, (void*)GCHandle.ToIntPtr(context), @@ -138,7 +138,7 @@ private unsafe QuicListener(QuicListenerOptions options) // Using the Unspecified family makes MsQuic handle connections from all IP addresses. address.Family = QUIC_ADDRESS_FAMILY_UNSPEC; } - ThrowIfFailure(MsQuicApi.Api.ApiTable->ListenerStart( + ThrowHelper.ThrowIfMsQuicError(MsQuicApi.Api.ApiTable->ListenerStart( _handle.QuicHandle, alpnBuffers.Buffers, (uint)alpnBuffers.Count, @@ -279,7 +279,7 @@ public async ValueTask DisposeAsync() _handle.Dispose(); // Flush the queue and dispose all remaining connections. - _acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(new QuicOperationAbortedException())); + _acceptQueue.Writer.TryComplete(ExceptionDispatchInfo.SetCurrentStackTrace(ThrowHelper.GetOperationAbortedException())); while (_acceptQueue.Reader.TryRead(out PendingConnection? pendingConnection)) { await pendingConnection.DisposeAsync().ConfigureAwait(false); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOperationAbortedException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOperationAbortedException.cs deleted file mode 100644 index 824fcb1ca297ab76b3038aee846e303ccf8e2682..0000000000000000000000000000000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicOperationAbortedException.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Quic -{ - public class QuicOperationAbortedException : QuicException - { - internal QuicOperationAbortedException() - : base(SR.net_quic_operationaborted) - { - } - - public QuicOperationAbortedException(string message) : base(message) - { - } - } -} diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStreamAbortedException.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStreamAbortedException.cs deleted file mode 100644 index 77d9ca356bb1bc8f75a73d98d391c72647de7b3f..0000000000000000000000000000000000000000 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicStreamAbortedException.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace System.Net.Quic -{ - public class QuicStreamAbortedException : QuicException - { - internal QuicStreamAbortedException(long errorCode) - : this(SR.Format(SR.net_quic_streamaborted, errorCode), errorCode) - { - } - - public QuicStreamAbortedException(string message, long errorCode) - : base(message) - { - ErrorCode = errorCode; - } - - public long ErrorCode { get; } - } -} diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index cd1f0061d3cc7fab498e77265b93e4e6ed1a4893..f6e485618a30531bea7e29b8313e14f1d86b8683 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -604,7 +604,7 @@ public async Task OpenStreamAsync_ConnectionAbort_Throws(bool unidirectional, bo else { await serverConnection.CloseAsync(0); - await Assert.ThrowsAsync(() => waitTask.AsTask().WaitAsync(TimeSpan.FromSeconds(3))); + await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, () => waitTask.AsTask().WaitAsync(TimeSpan.FromSeconds(3))); } clientConnection.Dispose(); @@ -624,7 +624,7 @@ public async Task SetListenerTimeoutWorksWithSmallTimeout() }; (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(null, listenerOptions); - await Assert.ThrowsAsync(async () => await serverConnection.AcceptStreamAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(100))); + await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, async () => await serverConnection.AcceptStreamAsync().AsTask().WaitAsync(TimeSpan.FromSeconds(100))); serverConnection.Dispose(); clientConnection.Dispose(); } @@ -746,7 +746,7 @@ public async Task CloseAsync_ByServer_AcceptThrows() var acceptTask = serverConnection.AcceptStreamAsync(); await serverConnection.CloseAsync(errorCode: 0); // make sure we throw - await Assert.ThrowsAsync(() => acceptTask.AsTask()); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => acceptTask.AsTask()); } } @@ -966,8 +966,8 @@ public async Task Read_ConnectionAbortedByPeer_Throws() await clientConnection.CloseAsync(ExpectedErrorCode); byte[] buffer = new byte[100]; - QuicConnectionAbortedException ex = await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, () => serverStream.ReadAsync(buffer).AsTask()); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); }).WaitAsync(TimeSpan.FromMilliseconds(PassingTestTimeoutMilliseconds)); } @@ -987,7 +987,7 @@ public async Task Read_ConnectionAbortedByUser_Throws() await serverConnection.CloseAsync(0); byte[] buffer = new byte[100]; - await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => serverStream.ReadAsync(buffer).AsTask()); }).WaitAsync(TimeSpan.FromMilliseconds(PassingTestTimeoutMilliseconds)); } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs index ce5ff99716a61c2f741bda7a3b05fa53d265ce81..c19188861e941c31c4806ee0ebff1daa95cc9c78 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicConnectionTests.cs @@ -74,7 +74,7 @@ public async Task CloseAsync_WithPendingAcceptAndConnect_PendingAndSubsequentThr sync.Release(); // Pending ops should fail - await Assert.ThrowsAsync(() => acceptTask); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => acceptTask); // TODO: This may not always throw QuicOperationAbortedException due to a data race with MsQuic worker threads // (CloseAsync may be processed before OpenStreamAsync as it is scheduled to the front of the operation queue) // To be revisited once we standartize on exceptions. @@ -83,7 +83,7 @@ public async Task CloseAsync_WithPendingAcceptAndConnect_PendingAndSubsequentThr // Subsequent attempts should fail // TODO: Which exception is correct? - await Assert.ThrowsAsync(async () => await serverConnection.AcceptStreamAsync()); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, async () => await serverConnection.AcceptStreamAsync()); await Assert.ThrowsAnyAsync(() => OpenAndUseStreamAsync(serverConnection)); }); } @@ -111,13 +111,13 @@ public async Task Dispose_WithPendingAcceptAndConnect_PendingAndSubsequentThrowO sync.Release(); // Pending ops should fail - await Assert.ThrowsAsync(() => acceptTask); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => acceptTask); // TODO: This may not always throw QuicOperationAbortedException due to a data race with MsQuic worker threads // (CloseAsync may be processed before OpenStreamAsync as it is scheduled to the front of the operation queue) // To be revisited once we standartize on exceptions. // [ActiveIssue("https://github.com/dotnet/runtime/issues/55619")] - await Assert.ThrowsAnyAsync(() => connectTask); + await Assert.ThrowsAsync(() => connectTask); // Subsequent attempts should fail // TODO: Should these be QuicOperationAbortedException, to match above? Or vice-versa? @@ -149,18 +149,18 @@ public async Task ConnectionClosedByPeer_WithPendingAcceptAndConnect_PendingAndS sync.Release(); // Pending ops should fail - QuicConnectionAbortedException ex; + QuicException ex; - ex = await Assert.ThrowsAsync(() => acceptTask); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); - ex = await Assert.ThrowsAsync(() => connectTask); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); + ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, () => acceptTask); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); + ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, () => connectTask); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); // Subsequent attempts should fail - ex = await Assert.ThrowsAsync(() => serverConnection.AcceptStreamAsync().AsTask()); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); - ex = await Assert.ThrowsAsync(() => OpenAndUseStreamAsync(serverConnection)); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); + ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, () => serverConnection.AcceptStreamAsync().AsTask()); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); + ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, () => OpenAndUseStreamAsync(serverConnection)); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); }); } @@ -199,8 +199,8 @@ public async Task CloseAsync_WithOpenStream_LocalAndPeerStreamsFailWithQuicOpera await clientConnection.CloseAsync(ExpectedErrorCode); - await Assert.ThrowsAsync(async () => await clientStream.ReadAsync(new byte[1])); - await Assert.ThrowsAsync(async () => await clientStream.WriteAsync(new byte[1])); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, async () => await clientStream.ReadAsync(new byte[1])); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, async () => await clientStream.WriteAsync(new byte[1])); }, async serverConnection => { @@ -210,11 +210,11 @@ public async Task CloseAsync_WithOpenStream_LocalAndPeerStreamsFailWithQuicOpera sync.Release(); // Since the peer did the abort, we should receive the abort error code in the exception. - QuicConnectionAbortedException ex; - ex = await Assert.ThrowsAsync(async () => await serverStream.ReadAsync(new byte[1])); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); - ex = await Assert.ThrowsAsync(async () => await serverStream.WriteAsync(new byte[1])); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); + QuicException ex; + ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, async () => await serverStream.ReadAsync(new byte[1])); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); + ex = await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, async () => await serverStream.WriteAsync(new byte[1])); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); }); } @@ -240,8 +240,8 @@ public async Task Dispose_WithOpenLocalStream_LocalStreamFailsWithQuicOperationA clientConnection.Dispose(); - await Assert.ThrowsAsync(async () => await clientStream.ReadAsync(new byte[1])); - await Assert.ThrowsAsync(async () => await clientStream.WriteAsync(new byte[1])); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, async () => await clientStream.ReadAsync(new byte[1])); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, async () => await clientStream.WriteAsync(new byte[1])); }, async serverConnection => { @@ -252,8 +252,8 @@ public async Task Dispose_WithOpenLocalStream_LocalStreamFailsWithQuicOperationA // The client has done an abortive shutdown of the connection, which means we are not notified that the connection has closed. // But the connection idle timeout should kick in and eventually we will get exceptions. - await Assert.ThrowsAsync(async () => await serverStream.ReadAsync(new byte[1])); - await Assert.ThrowsAsync(async () => await serverStream.WriteAsync(new byte[1])); + await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, async () => await serverStream.ReadAsync(new byte[1])); + await AssertThrowsQuicExceptionAsync(QuicError.ConnectionAborted, async () => await serverStream.WriteAsync(new byte[1])); }, listenerOptions: listenerOptions); } } diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs index 6269c5fab80b5acd6c5b530ab4e4c8726b5aafdf..5101ade6a61dbd43af76a1dc6af700bbac887115 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicStreamTests.cs @@ -459,8 +459,8 @@ public async Task Read_WriteAborted_Throws() sem.Release(); byte[] buffer = new byte[100]; - QuicStreamAbortedException ex = await Assert.ThrowsAsync(() => serverStream.ReadAsync(buffer).AsTask()); - Assert.Equal(ExpectedErrorCode, ex.ErrorCode); + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => serverStream.ReadAsync(buffer).AsTask()); + Assert.Equal(ExpectedErrorCode, ex.ApplicationErrorCode); }); } @@ -519,7 +519,7 @@ public async Task ReadOutstanding_ReadAborted_Throws() using (clientStream) using (serverStream) { - Task exTask = Assert.ThrowsAsync(() => serverStream.ReadAsync(new byte[1]).AsTask()); + Task exTask = AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => serverStream.ReadAsync(new byte[1]).AsTask()); Assert.False(exTask.IsCompleted); serverStream.AbortRead(ExpectedErrorCode); @@ -546,8 +546,8 @@ public async Task WriteAbortedWithoutWriting_ReadThrows() byte[] buffer = new byte[1]; - QuicStreamAbortedException ex = await Assert.ThrowsAsync(() => ReadAll(stream, buffer)); - Assert.Equal(expectedErrorCode, ex.ErrorCode); + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => ReadAll(stream, buffer)); + Assert.Equal(expectedErrorCode, ex.ApplicationErrorCode); // We should still return true from CanRead, even though the read has been aborted. Assert.True(stream.CanRead); @@ -570,8 +570,8 @@ public async Task ReadAbortedWithoutReading_WriteThrows() { await using QuicStream stream = await connection.AcceptStreamAsync(); - QuicStreamAbortedException ex = await Assert.ThrowsAsync(() => WriteForever(stream)); - Assert.Equal(expectedErrorCode, ex.ErrorCode); + QuicException ex = await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => WriteForever(stream)); + Assert.Equal(expectedErrorCode, ex.ApplicationErrorCode); // We should still return true from CanWrite, even though the write has been aborted. Assert.True(stream.CanWrite); @@ -595,7 +595,7 @@ public async Task WritePreCanceled_Throws() await Assert.ThrowsAsync(() => stream.WriteAsync(new byte[1], cts.Token).AsTask()); // aborting write causes the write direction to throw on subsequent operations - await Assert.ThrowsAsync(() => stream.WriteAsync(new byte[1]).AsTask()); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => stream.WriteAsync(new byte[1]).AsTask()); // manual write abort is still required stream.AbortWrite(expectedErrorCode); @@ -608,7 +608,7 @@ public async Task WritePreCanceled_Throws() byte[] buffer = new byte[1024 * 1024]; - QuicStreamAbortedException ex = await Assert.ThrowsAsync(() => ReadAll(stream, buffer)); + await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => ReadAll(stream, buffer)); await stream.ShutdownCompleted(); } @@ -640,7 +640,7 @@ async Task WriteUntilCanceled() await Assert.ThrowsAsync(() => WriteUntilCanceled().WaitAsync(TimeSpan.FromSeconds(3))); // next write would also throw - await Assert.ThrowsAsync(() => stream.WriteAsync(new byte[1]).AsTask()); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => stream.WriteAsync(new byte[1]).AsTask()); // manual write abort is still required stream.AbortWrite(expectedErrorCode); @@ -664,7 +664,7 @@ async Task ReadUntilAborted() } } - QuicStreamAbortedException ex = await Assert.ThrowsAsync(() => ReadUntilAborted()); + await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => ReadUntilAborted()); await stream.ShutdownCompleted(); } @@ -772,9 +772,9 @@ async ValueTask ReleaseOnWriteCompletionAsync() await serverStream.WaitForWriteCompletionAsync(); waitForAbortTcs.SetException(new Exception("WaitForWriteCompletionAsync didn't throw stream aborted.")); } - catch (QuicStreamAbortedException ex) + catch (QuicException ex) when (ex.QuicError == QuicError.StreamAborted) { - waitForAbortTcs.SetResult(ex.ErrorCode); + waitForAbortTcs.SetResult(ex.ApplicationErrorCode.Value); } catch (Exception ex) { @@ -805,7 +805,7 @@ public async Task WriteAsync_LocalAbort_Throws() var writeTask = WriteForever(serverStream, 1024 * 1024); serverStream.AbortWrite(ExpectedErrorCode); - await Assert.ThrowsAsync(() => writeTask.WaitAsync(TimeSpan.FromSeconds(3))); + await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => writeTask.WaitAsync(TimeSpan.FromSeconds(3))); sem.Release(); }); } @@ -845,7 +845,7 @@ async ValueTask ReleaseOnWriteCompletionAsync() await serverStream.WaitForWriteCompletionAsync(); waitForAbortTcs.SetException(new Exception("WaitForWriteCompletionAsync didn't throw stream aborted.")); } - catch (QuicOperationAbortedException) + catch (QuicException ex) when (ex.QuicError == QuicError.OperationAborted) { waitForAbortTcs.SetResult(); } @@ -956,9 +956,9 @@ async ValueTask ReleaseOnWriteCompletionAsync() await stream.WaitForWriteCompletionAsync(); waitForAbortTcs.SetException(new Exception("WaitForWriteCompletionAsync didn't throw connection aborted.")); } - catch (QuicConnectionAbortedException ex) + catch (QuicException ex) when (ex.QuicError == QuicError.ConnectionAborted) { - waitForAbortTcs.SetResult(ex.ErrorCode); + waitForAbortTcs.SetResult(ex.ApplicationErrorCode.Value); } }; }, diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs index ddf1e8e1a755e7344f6ea5898bdab571d0cc155b..53685907d9e9ad52dfa9d4aa8a8e44ef993152e1 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicTestBase.cs @@ -42,6 +42,13 @@ public bool RemoteCertificateValidationCallback(object sender, X509Certificate? return true; } + public async Task AssertThrowsQuicExceptionAsync(QuicError expectedError, Func testCode) + { + QuicException ex = await Assert.ThrowsAsync(testCode); + Assert.Equal(expectedError, ex.QuicError); + return ex; + } + public QuicServerConnectionOptions CreateQuicServerOptions() { return new QuicServerConnectionOptions()