未验证 提交 aa156dbb 编写于 作者: R Radek Zikmund 提交者: GitHub

Implement new QuicException proposal (#71432)

* Add QuicException as in proposal

* Map other MsQuic errors to QuicException

* Fix System.Net.Http

* Always set HResult in QUIC-related exception

* Fix possible incorrect exception when writing into aborted stream

* Code review feedback

* Fix ref files

* Use latest generated interop

* Remove MsQuicException

* Code review feedback

* Remove TODO

* move ThrowHelper to Internal

* Code review changes

* Minor change
上级 2948bb9a
......@@ -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<QuicConnectionAbortedException>(async () => await _inboundControlStream.ReadFrameAsync());
QuicException ex = await Assert.ThrowsAsync<QuicException>(async () => await _inboundControlStream.ReadFrameAsync());
Assert.Equal(QuicError.ConnectionAborted, ex.QuicError);
await CloseAsync(H3_NO_ERROR);
}
......
......@@ -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;
}
......
......@@ -196,7 +196,7 @@ public async Task<HttpResponseMessage> 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<HttpResponseMessage> 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;
......
......@@ -228,23 +228,27 @@ public async Task<HttpResponseMessage> 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<bool> ReadNextDataFrameAsync(HttpResponseMessage response, CancellationToken cancellationToken)
......
......@@ -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) { }
});
}
......
......@@ -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<QuicConnectionAbortedException>(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<QuicStreamAbortedException>(() => 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<IOException>(ex);
var hre = Assert.IsType<HttpRequestException>(ioe.InnerException);
Assert.IsType<QuicOperationAbortedException>(hre.InnerException);
var qex = Assert.IsType<QuicException>(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<QuicStreamAbortedException>(() => 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<IOException>(ex);
var hre = Assert.IsType<HttpRequestException>(ioe.InnerException);
Assert.IsType<QuicOperationAbortedException>(hre.InnerException);
var qex = Assert.IsType<QuicException>(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<HttpRequestException>(() => client.SendAsync(request).WaitAsync(TimeSpan.FromSeconds(10)));
Assert.Contains("ALPN_NEG_FAILURE", ex.Message);
Assert.IsType<AuthenticationException>(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<QuicStreamAbortedException>(() => 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<QuicStreamAbortedException>(() => 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<QuicException> AssertThrowsQuicExceptionAsync(QuicError expectedError, Func<Task> testCode)
{
QuicException ex = await Assert.ThrowsAsync<QuicException>(testCode);
Assert.Equal(expectedError, ex.QuicError);
return ex;
}
public static TheoryData<HttpStatusCode, bool> StatusCodesTestData()
{
var statuses = Enum.GetValues(typeof(HttpStatusCode)).Cast<HttpStatusCode>().Where(s => s >= HttpStatusCode.OK); // exclude informational
......
......@@ -32,11 +32,6 @@ public sealed partial class QuicConnection : System.IDisposable
public System.Threading.Tasks.ValueTask<System.Net.Quic.QuicStream> OpenBidirectionalStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
public System.Threading.Tasks.ValueTask<System.Net.Quic.QuicStream> 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<byte> 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; } }
}
}
......@@ -129,8 +129,11 @@
<data name="net_quic_reading_notallowed" xml:space="preserve">
<value>Reading is not allowed on stream.</value>
</data>
<data name="net_quic_sending_aborted" xml:space="preserve">
<value>Sending has already been aborted on the stream</value>
<data name="net_quic_writing_aborted" xml:space="preserve">
<value>Writing has been aborted on the stream.</value>
</data>
<data name="net_quic_reading_aborted" xml:space="preserve">
<value>Reading has been aborted on the stream.</value>
</data>
<data name="net_quic_streamaborted" xml:space="preserve">
<value>Stream aborted by peer ({0}).</value>
......@@ -148,7 +151,7 @@
<value>Timeout can only be set to 'System.Threading.Timeout.Infinite' or a value &gt; 0.</value>
</data>
<data name="net_quic_timeout" xml:space="preserve">
<value>Connection timed out.</value>
<value>Connection timed out waiting for a response from the peer.</value>
</data>
<data name="net_quic_ssl_option" xml:space="preserve">
<value>'{0}' is not supported by System.Net.Quic.</value>
......@@ -162,6 +165,9 @@
<data name="net_quic_not_connected" xml:space="preserve">
<value>Connection is not connected.</value>
</data>
<data name="net_quic_internal_error" xml:space="preserve">
<value>An internal error has occured. {0}</value>
</data>
<data name="net_ssl_app_protocols_invalid" xml:space="preserve">
<value>The application protocol list is invalid.</value>
</data>
......@@ -171,8 +177,39 @@
<data name="net_quic_empty_cipher_suite" xml:space="preserve">
<value>CipherSuitePolicy must specify at least one cipher supported by QUIC.</value>
</data>
<data name="net_quic_address_in_use" xml:space="preserve">
<value>The local address is already in use.</value>
</data>
<data name="net_quic_host_unreachable" xml:space="preserve">
<value>The server is currnetly unreachable.</value>
</data>
<data name="net_quic_connection_refused" xml:space="preserve">
<value>The server refused the connection.</value>
</data>
<data name="net_quic_protocol_error" xml:space="preserve">
<value>A QUIC protocol error was encountered</value>
</data>
<data name="net_quic_ver_neg_error" xml:space="preserve">
<value>A version negotiation error was encountered.</value>
</data>
<data name="net_quic_alpn_neg_error" xml:space="preserve">
<value>Application layer protocol negotiation error was encountered.</value>
</data>
<data name="net_quic_connection_idle" xml:space="preserve">
<value>The connection timed out from inactivity.</value>
</data>
<data name="net_quic_invalid_address" xml:space="preserve">
<value>Binding to socket failed, likely caused by a family mismatch between local and remote address.</value>
</data>
<data name="net_quic_auth" xml:space="preserve">
<value>Authentication failed. {0}</value>
</data>
<!-- Same as in System.Net.Security -->
<data name="net_io_invalidnestedcall" xml:space="preserve">
<value> This method may not be called when another {0} operation is pending.</value>
<value>This method may not be called when another {0} operation is pending.</value>
</data>
<data name="net_auth_tls_alert" xml:space="preserve">
<value>Authentication failed because the remote party sent a TLS alert: '{0}'.</value>
</data>
<!-- Referenced in shared IPEndPointExtensions.cs-->
<data name="net_InvalidAddressFamily" xml:space="preserve">
......
......@@ -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);
}
......
......@@ -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),
......
// 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;
}
}
}
......@@ -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
{
......
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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;
......
......@@ -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));
}
......
......@@ -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<TBuffer>(Action<State, TBuffer> 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<TBuffer>(Action<State, TBuffer> 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<TBuffer>(Action<State, TBuffer> 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");
}
/// <summary>
......@@ -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;
}
......
// 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.
};
}
}
}
// 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})";
}
}
}
// 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; }
}
}
// 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
{
/// <summary>
/// Defines the various error conditions for <see cref="QuicListener"/>, <see cref="QuicConnection"/> and <see cref="QuicStream"/> operations.
/// </summary>
public enum QuicError
{
/// <summary>
/// No error.
/// </summary>
Success,
/// <summary>
/// An internal implementation error has occured.
/// </summary>
InternalError,
/// <summary>
/// The connection was aborted by the peer. This error is associated with an application-level error code.
/// </summary>
ConnectionAborted,
/// <summary>
/// The read or write direction of the stream was aborted by the peer. This error is associated with an application-level error code.
/// </summary>
StreamAborted,
/// <summary>
/// The local address is already in use.
/// </summary>
AddressInUse,
/// <summary>
/// Binding to socket failed, likely caused by a family mismatch between local and remote address.
/// </summary>
InvalidAddress,
/// <summary>
/// The connection timed out waiting for a response from the peer.
/// </summary>
ConnectionTimeout,
/// <summary>
/// The server is currently unreachable.
/// </summary>
HostUnreachable,
/// <summary>
/// The server refused the connection.
/// </summary>
ConnectionRefused,
/// <summary>
/// A version negotiation error was encountered.
/// </summary>
VersionNegotiationError,
/// <summary>
/// The connection timed out from inactivity.
/// </summary>
ConnectionIdle,
/// <summary>
/// A QUIC protocol error was encountered.
/// </summary>
ProtocolError,
/// <summary>
/// The operation has been aborted.
/// </summary>
OperationAborted,
}
}
// 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
/// <summary>
/// The exception that is thrown when a QUIC error occurs.
/// </summary>
public sealed class QuicException : IOException
{
public QuicException(string? message)
/// <summary>
/// Initializes a new instance of the <see cref='QuicException'/> class.
/// </summary>
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;
}
}
/// <summary>
/// Gets the error which is associated with this exception.
/// </summary>
public QuicError QuicError { get; }
/// <summary>
/// The application protocol error code associated with the error.
/// </summary>
/// <remarks>
/// This property contains the error code set by the application layer when closing the connection (<see cref="QuicError.ConnectionAborted"/>) or closing a read/write direction of a QUIC stream (<see cref="QuicError.StreamAborted"/>). Contains null for all other errors.
/// </remarks>
public long? ApplicationErrorCode { get; }
}
}
......@@ -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);
......
// 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)
{
}
}
}
// 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; }
}
}
......@@ -604,7 +604,7 @@ public async Task OpenStreamAsync_ConnectionAbort_Throws(bool unidirectional, bo
else
{
await serverConnection.CloseAsync(0);
await Assert.ThrowsAsync<QuicConnectionAbortedException>(() => 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<QuicConnectionAbortedException>(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<QuicOperationAbortedException>(() => 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<QuicConnectionAbortedException>(() => 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<QuicOperationAbortedException>(() => serverStream.ReadAsync(buffer).AsTask());
await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, () => serverStream.ReadAsync(buffer).AsTask());
}).WaitAsync(TimeSpan.FromMilliseconds(PassingTestTimeoutMilliseconds));
}
......
......@@ -74,7 +74,7 @@ public async Task CloseAsync_WithPendingAcceptAndConnect_PendingAndSubsequentThr
sync.Release();
// Pending ops should fail
await Assert.ThrowsAsync<QuicOperationAbortedException>(() => 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<QuicOperationAbortedException>(async () => await serverConnection.AcceptStreamAsync());
await AssertThrowsQuicExceptionAsync(QuicError.OperationAborted, async () => await serverConnection.AcceptStreamAsync());
await Assert.ThrowsAnyAsync<QuicException>(() => OpenAndUseStreamAsync(serverConnection));
});
}
......@@ -111,13 +111,13 @@ public async Task Dispose_WithPendingAcceptAndConnect_PendingAndSubsequentThrowO
sync.Release();
// Pending ops should fail
await Assert.ThrowsAsync<QuicOperationAbortedException>(() => 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<QuicException>(() => connectTask);
await Assert.ThrowsAsync<QuicException>(() => 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<QuicConnectionAbortedException>(() => acceptTask);
Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
ex = await Assert.ThrowsAsync<QuicConnectionAbortedException>(() => 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<QuicConnectionAbortedException>(() => serverConnection.AcceptStreamAsync().AsTask());
Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
ex = await Assert.ThrowsAsync<QuicConnectionAbortedException>(() => 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<QuicOperationAbortedException>(async () => await clientStream.ReadAsync(new byte[1]));
await Assert.ThrowsAsync<QuicOperationAbortedException>(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<QuicConnectionAbortedException>(async () => await serverStream.ReadAsync(new byte[1]));
Assert.Equal(ExpectedErrorCode, ex.ErrorCode);
ex = await Assert.ThrowsAsync<QuicConnectionAbortedException>(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<QuicOperationAbortedException>(async () => await clientStream.ReadAsync(new byte[1]));
await Assert.ThrowsAsync<QuicOperationAbortedException>(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<QuicConnectionAbortedException>(async () => await serverStream.ReadAsync(new byte[1]));
await Assert.ThrowsAsync<QuicConnectionAbortedException>(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);
}
}
......
......@@ -459,8 +459,8 @@ public async Task Read_WriteAborted_Throws()
sem.Release();
byte[] buffer = new byte[100];
QuicStreamAbortedException ex = await Assert.ThrowsAsync<QuicStreamAbortedException>(() => 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<QuicOperationAbortedException>(() => 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<QuicStreamAbortedException>(() => 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<QuicStreamAbortedException>(() => 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<OperationCanceledException>(() => stream.WriteAsync(new byte[1], cts.Token).AsTask());
// aborting write causes the write direction to throw on subsequent operations
await Assert.ThrowsAsync<QuicOperationAbortedException>(() => 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<QuicStreamAbortedException>(() => ReadAll(stream, buffer));
await AssertThrowsQuicExceptionAsync(QuicError.StreamAborted, () => ReadAll(stream, buffer));
await stream.ShutdownCompleted();
}
......@@ -640,7 +640,7 @@ async Task WriteUntilCanceled()
await Assert.ThrowsAsync<OperationCanceledException>(() => WriteUntilCanceled().WaitAsync(TimeSpan.FromSeconds(3)));
// next write would also throw
await Assert.ThrowsAsync<QuicOperationAbortedException>(() => 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<QuicStreamAbortedException>(() => 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<QuicOperationAbortedException>(() => 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);
}
};
},
......
......@@ -42,6 +42,13 @@ public bool RemoteCertificateValidationCallback(object sender, X509Certificate?
return true;
}
public async Task<QuicException> AssertThrowsQuicExceptionAsync(QuicError expectedError, Func<Task> testCode)
{
QuicException ex = await Assert.ThrowsAsync<QuicException>(testCode);
Assert.Equal(expectedError, ex.QuicError);
return ex;
}
public QuicServerConnectionOptions CreateQuicServerOptions()
{
return new QuicServerConnectionOptions()
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册