Skip to content

Commit 912f2ed

Browse files
committed
[QUIC] API QuicConnection (dotnet#71783)
* Listener comment; PreviewFeature attribute * Feedback * QuicConnection new API including compilable implementation * Fixed logging * Fixed S.N.Quic and S.N.Http tests * Options now correspond to the issue * Feedback * Comments, PreviewFeature attribute and RemoteCertificate disposal. * Preview feature attribute is assembly wide * Some typos * Fixed test with certificate * Default values as constants * Event handlers split into methods called via switch expression. * Some more comments * Unified unsafe usage * Fixed some more tests * Cleaned up some exceptions and resource strings. * Feedback * Latest greatest API proposal. * Fixed Http solution * Feedback
1 parent d6a7be2 commit 912f2ed

37 files changed

+1667
-1683
lines changed

src/libraries/Common/tests/System/Net/Http/Http3LoopbackConnection.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ public async Task CloseAsync(long errorCode)
9191

9292
public async ValueTask<Http3LoopbackStream> OpenUnidirectionalStreamAsync()
9393
{
94-
return new Http3LoopbackStream(await _connection.OpenUnidirectionalStreamAsync());
94+
return new Http3LoopbackStream(await _connection.OpenOutboundStreamAsync(QuicStreamType.Unidirectional));
9595
}
9696

9797
public async ValueTask<Http3LoopbackStream> OpenBidirectionalStreamAsync()
9898
{
99-
return new Http3LoopbackStream(await _connection.OpenBidirectionalStreamAsync());
99+
return new Http3LoopbackStream(await _connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional));
100100
}
101101

102102
public static int GetRequestId(QuicStream stream)
@@ -131,7 +131,7 @@ async Task EnsureControlStreamAcceptedInternalAsync()
131131

132132
while (true)
133133
{
134-
QuicStream quicStream = await _connection.AcceptStreamAsync().ConfigureAwait(false);
134+
QuicStream quicStream = await _connection.AcceptInboundStreamAsync().ConfigureAwait(false);
135135

136136
if (!quicStream.CanWrite)
137137
{
@@ -165,7 +165,7 @@ public async Task<Http3LoopbackStream> AcceptRequestStreamAsync()
165165

166166
if (!_delayedStreams.TryDequeue(out QuicStream quicStream))
167167
{
168-
quicStream = await _connection.AcceptStreamAsync().ConfigureAwait(false);
168+
quicStream = await _connection.AcceptInboundStreamAsync().ConfigureAwait(false);
169169
}
170170

171171
var stream = new Http3LoopbackStream(quicStream);

src/libraries/Common/tests/System/Net/Http/Http3LoopbackServer.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,10 @@ public Http3LoopbackServer(Http3Options options = null)
3636
{
3737
var serverOptions = new QuicServerConnectionOptions()
3838
{
39-
MaxBidirectionalStreams = options.MaxBidirectionalStreams,
40-
MaxUnidirectionalStreams = options.MaxUnidirectionalStreams,
39+
DefaultStreamErrorCode = Http3LoopbackConnection.H3_REQUEST_CANCELLED,
40+
DefaultCloseErrorCode = Http3LoopbackConnection.H3_NO_ERROR,
41+
MaxInboundBidirectionalStreams = options.MaxInboundBidirectionalStreams,
42+
MaxInboundUnidirectionalStreams = options.MaxInboundUnidirectionalStreams,
4143
ServerAuthenticationOptions = new SslServerAuthenticationOptions
4244
{
4345
EnabledSslProtocols = options.SslProtocols,
@@ -136,16 +138,16 @@ private static Http3Options CreateOptions(GenericLoopbackOptions options)
136138
}
137139
public class Http3Options : GenericLoopbackOptions
138140
{
139-
public int MaxUnidirectionalStreams { get; set; }
141+
public int MaxInboundUnidirectionalStreams { get; set; }
140142

141-
public int MaxBidirectionalStreams { get; set; }
143+
public int MaxInboundBidirectionalStreams { get; set; }
142144

143145
public string Alpn { get; set; }
144146

145147
public Http3Options()
146148
{
147-
MaxUnidirectionalStreams = 10;
148-
MaxBidirectionalStreams = 100;
149+
MaxInboundUnidirectionalStreams = 10;
150+
MaxInboundBidirectionalStreams = 100;
149151
Alpn = SslApplicationProtocol.Http3.ToString();
150152
}
151153
}

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/ConnectHelper.cs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,22 +107,22 @@ public static async ValueTask<SslStream> EstablishSslConnectionAsync(SslClientAu
107107
public static async ValueTask<QuicConnection> ConnectQuicAsync(HttpRequestMessage request, DnsEndPoint endPoint, TimeSpan idleTimeout, SslClientAuthenticationOptions clientAuthenticationOptions, CancellationToken cancellationToken)
108108
{
109109
clientAuthenticationOptions = SetUpRemoteCertificateValidationCallback(clientAuthenticationOptions, request);
110-
QuicConnection connection = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions()
111-
{
112-
MaxBidirectionalStreams = 0, // Client doesn't support inbound streams: https://www.rfc-editor.org/rfc/rfc9114.html#name-bidirectional-streams. An extension might change this.
113-
MaxUnidirectionalStreams = 5, // Minimum is 3: https://www.rfc-editor.org/rfc/rfc9114.html#unidirectional-streams (1x control stream + 2x QPACK). Set to 100 if/when support for PUSH streams is added.
114-
IdleTimeout = idleTimeout,
115-
RemoteEndPoint = endPoint,
116-
ClientAuthenticationOptions = clientAuthenticationOptions
117-
}, cancellationToken).ConfigureAwait(false);
110+
118111
try
119112
{
120-
await connection.ConnectAsync(cancellationToken).ConfigureAwait(false);
121-
return connection;
113+
return await QuicConnection.ConnectAsync(new QuicClientConnectionOptions()
114+
{
115+
MaxInboundBidirectionalStreams = 0, // Client doesn't support inbound streams: https://www.rfc-editor.org/rfc/rfc9114.html#name-bidirectional-streams. An extension might change this.
116+
MaxInboundUnidirectionalStreams = 5, // Minimum is 3: https://www.rfc-editor.org/rfc/rfc9114.html#unidirectional-streams (1x control stream + 2x QPACK). Set to 100 if/when support for PUSH streams is added.
117+
IdleTimeout = idleTimeout,
118+
DefaultStreamErrorCode = (long)Http3ErrorCode.RequestCancelled,
119+
DefaultCloseErrorCode = (long)Http3ErrorCode.NoError,
120+
RemoteEndPoint = endPoint,
121+
ClientAuthenticationOptions = clientAuthenticationOptions
122+
}, cancellationToken).ConfigureAwait(false);
122123
}
123124
catch (Exception ex)
124125
{
125-
connection.Dispose();
126126
throw CreateWrappedException(ex, endPoint.Host, endPoint.Port, cancellationToken);
127127
}
128128
}

src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ private void CheckForShutdown()
132132
QuicConnection connection = _connection;
133133
_connection = null;
134134

135-
_ = _connectionClosedTask.ContinueWith(closeTask =>
135+
_ = _connectionClosedTask.ContinueWith(async closeTask =>
136136
{
137137
if (closeTask.IsFaulted && NetEventSource.Log.IsEnabled())
138138
{
@@ -141,7 +141,7 @@ private void CheckForShutdown()
141141

142142
try
143143
{
144-
connection.Dispose();
144+
await connection.DisposeAsync().ConfigureAwait(false);
145145
}
146146
catch (Exception ex)
147147
{
@@ -184,7 +184,7 @@ public async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, lon
184184
queueStartingTimestamp = Stopwatch.GetTimestamp();
185185
}
186186

187-
quicStream = await conn.OpenBidirectionalStreamAsync(cancellationToken).ConfigureAwait(false);
187+
quicStream = await conn.OpenOutboundStreamAsync(QuicStreamType.Bidirectional, cancellationToken).ConfigureAwait(false);
188188

189189
requestStream = new Http3RequestStream(request, this, quicStream);
190190
lock (SyncObj)
@@ -366,7 +366,7 @@ private async Task SendSettingsAsync()
366366
{
367367
try
368368
{
369-
_clientControl = await _connection!.OpenUnidirectionalStreamAsync().ConfigureAwait(false);
369+
_clientControl = await _connection!.OpenOutboundStreamAsync(QuicStreamType.Unidirectional).ConfigureAwait(false);
370370
await _clientControl.WriteAsync(_pool.Settings.Http3SettingsFrame, CancellationToken.None).ConfigureAwait(false);
371371
}
372372
catch (Exception ex)
@@ -410,7 +410,7 @@ private async Task AcceptStreamsAsync()
410410
}
411411

412412
// No cancellation token is needed here; we expect the operation to cancel itself when _connection is disposed.
413-
streamTask = _connection!.AcceptStreamAsync(CancellationToken.None);
413+
streamTask = _connection!.AcceptInboundStreamAsync(CancellationToken.None);
414414
}
415415

416416
QuicStream stream = await streamTask.ConfigureAwait(false);

src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http3.cs

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public async Task ClientSettingsReceived_Success(int headerSizeLimit)
8181
[InlineData(1000)]
8282
public async Task SendMoreThanStreamLimitRequests_Succeeds(int streamLimit)
8383
{
84-
using Http3LoopbackServer server = CreateHttp3LoopbackServer(new Http3Options() { MaxBidirectionalStreams = streamLimit });
84+
using Http3LoopbackServer server = CreateHttp3LoopbackServer(new Http3Options() { MaxInboundBidirectionalStreams = streamLimit });
8585

8686
Task serverTask = Task.Run(async () =>
8787
{
@@ -119,7 +119,7 @@ public async Task SendMoreThanStreamLimitRequests_Succeeds(int streamLimit)
119119
[InlineData(1000)]
120120
public async Task SendStreamLimitRequestsConcurrently_Succeeds(int streamLimit)
121121
{
122-
using Http3LoopbackServer server = CreateHttp3LoopbackServer(new Http3Options() { MaxBidirectionalStreams = streamLimit });
122+
using Http3LoopbackServer server = CreateHttp3LoopbackServer(new Http3Options() { MaxInboundBidirectionalStreams = streamLimit });
123123

124124
Task serverTask = Task.Run(async () =>
125125
{
@@ -165,7 +165,7 @@ public async Task SendStreamLimitRequestsConcurrently_Succeeds(int streamLimit)
165165
[InlineData(1000)]
166166
public async Task SendMoreThanStreamLimitRequestsConcurrently_LastWaits(int streamLimit)
167167
{
168-
using Http3LoopbackServer server = CreateHttp3LoopbackServer(new Http3Options() { MaxBidirectionalStreams = streamLimit });
168+
using Http3LoopbackServer server = CreateHttp3LoopbackServer(new Http3Options() { MaxInboundBidirectionalStreams = streamLimit });
169169
var lastRequestContentStarted = new TaskCompletionSource();
170170

171171
Task serverTask = Task.Run(async () =>
@@ -890,31 +890,10 @@ public async Task Alpn_NonH3_NegotiationFailure()
890890

891891
private SslApplicationProtocol ExtractMsQuicNegotiatedAlpn(Http3LoopbackConnection loopbackConnection)
892892
{
893-
// TODO: rewrite after object structure change
894-
// current structure:
895-
// Http3LoopbackConnection -> private QuicConnection _connection
896-
// QuicConnection -> private QuicConnectionProvider _provider (= MsQuicConnection)
897-
// MsQuicConnection -> private SslApplicationProtocol _negotiatedAlpnProtocol
898-
899893
FieldInfo quicConnectionField = loopbackConnection.GetType().GetField("_connection", BindingFlags.Instance | BindingFlags.NonPublic);
900894
Assert.NotNull(quicConnectionField);
901-
object quicConnection = quicConnectionField.GetValue(loopbackConnection);
902-
Assert.NotNull(quicConnection);
903-
Assert.Equal("QuicConnection", quicConnection.GetType().Name);
904-
905-
FieldInfo msQuicConnectionField = quicConnection.GetType().GetField("_provider", BindingFlags.Instance | BindingFlags.NonPublic);
906-
Assert.NotNull(msQuicConnectionField);
907-
object msQuicConnection = msQuicConnectionField.GetValue(quicConnection);
908-
Assert.NotNull(msQuicConnection);
909-
Assert.Equal("MsQuicConnection", msQuicConnection.GetType().Name);
910-
911-
FieldInfo alpnField = msQuicConnection.GetType().GetField("_negotiatedAlpnProtocol", BindingFlags.Instance | BindingFlags.NonPublic);
912-
Assert.NotNull(alpnField);
913-
object alpn = alpnField.GetValue(msQuicConnection);
914-
Assert.NotNull(alpn);
915-
Assert.IsType<SslApplicationProtocol>(alpn);
916-
917-
return (SslApplicationProtocol)alpn;
895+
QuicConnection quicConnection = Assert.IsType<QuicConnection>(quicConnectionField.GetValue(loopbackConnection));
896+
return quicConnection.NegotiatedApplicationProtocol;
918897
}
919898

920899
[Theory]

src/libraries/System.Net.Http/tests/FunctionalTests/TelemetryTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ await GetFactoryForVersion(version).CreateClientAndServerAsync(
682682
await connection.ReadRequestDataAsync(readBody: false);
683683
await connection.SendResponseAsync();
684684
};
685-
}, options: new Http3Options { MaxBidirectionalStreams = 1 });
685+
}, options: new Http3Options { MaxInboundBidirectionalStreams = 1 });
686686

687687
await WaitForEventCountersAsync(events);
688688
});

src/libraries/System.Net.Quic/ref/System.Net.Quic.cs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,35 +9,33 @@ namespace System.Net.Quic
99
public sealed partial class QuicClientConnectionOptions : System.Net.Quic.QuicConnectionOptions
1010
{
1111
public QuicClientConnectionOptions() { }
12-
public required System.Net.Security.SslClientAuthenticationOptions ClientAuthenticationOptions { get { throw null; } set { } }
12+
public System.Net.Security.SslClientAuthenticationOptions ClientAuthenticationOptions { get { throw null; } set { } }
1313
public System.Net.IPEndPoint? LocalEndPoint { get { throw null; } set { } }
14-
public required System.Net.EndPoint RemoteEndPoint { get { throw null; } set { } }
14+
public System.Net.EndPoint RemoteEndPoint { get { throw null; } set { } }
1515
}
16-
public sealed partial class QuicConnection : System.IDisposable
16+
public sealed partial class QuicConnection : System.IAsyncDisposable
1717
{
1818
internal QuicConnection() { }
19-
public bool Connected { get { throw null; } }
2019
public static bool IsSupported { get { throw null; } }
21-
public System.Net.IPEndPoint? LocalEndPoint { get { throw null; } }
20+
public System.Net.IPEndPoint LocalEndPoint { get { throw null; } }
2221
public System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol { get { throw null; } }
2322
public System.Security.Cryptography.X509Certificates.X509Certificate? RemoteCertificate { get { throw null; } }
24-
public System.Net.EndPoint RemoteEndPoint { get { throw null; } }
25-
public System.Threading.Tasks.ValueTask<System.Net.Quic.QuicStream> AcceptStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
23+
public System.Net.IPEndPoint RemoteEndPoint { get { throw null; } }
24+
public System.Threading.Tasks.ValueTask<System.Net.Quic.QuicStream> AcceptInboundStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
2625
public System.Threading.Tasks.ValueTask CloseAsync(long errorCode, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
2726
public static System.Threading.Tasks.ValueTask<System.Net.Quic.QuicConnection> ConnectAsync(System.Net.Quic.QuicClientConnectionOptions options, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
28-
public System.Threading.Tasks.ValueTask ConnectAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
29-
public void Dispose() { }
30-
public int GetRemoteAvailableBidirectionalStreamCount() { throw null; }
31-
public int GetRemoteAvailableUnidirectionalStreamCount() { throw null; }
32-
public System.Threading.Tasks.ValueTask<System.Net.Quic.QuicStream> OpenBidirectionalStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
33-
public System.Threading.Tasks.ValueTask<System.Net.Quic.QuicStream> OpenUnidirectionalStreamAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
27+
public System.Threading.Tasks.ValueTask DisposeAsync() { throw null; }
28+
public System.Threading.Tasks.ValueTask<System.Net.Quic.QuicStream> OpenOutboundStreamAsync(System.Net.Quic.QuicStreamType type, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
29+
public override string ToString() { throw null; }
3430
}
3531
public abstract partial class QuicConnectionOptions
3632
{
3733
internal QuicConnectionOptions() { }
34+
public long DefaultCloseErrorCode { get { throw null; } set { } }
35+
public long DefaultStreamErrorCode { get { throw null; } set { } }
3836
public System.TimeSpan IdleTimeout { get { throw null; } set { } }
39-
public int MaxBidirectionalStreams { get { throw null; } set { } }
40-
public int MaxUnidirectionalStreams { get { throw null; } set { } }
37+
public int MaxInboundBidirectionalStreams { get { throw null; } set { } }
38+
public int MaxInboundUnidirectionalStreams { get { throw null; } set { } }
4139
}
4240
public enum QuicError
4341
{
@@ -74,15 +72,15 @@ internal QuicListener() { }
7472
public sealed partial class QuicListenerOptions
7573
{
7674
public QuicListenerOptions() { }
77-
public required System.Collections.Generic.List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get { throw null; } set { } }
78-
public required System.Func<System.Net.Quic.QuicConnection, System.Net.Security.SslClientHelloInfo, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<System.Net.Quic.QuicServerConnectionOptions>> ConnectionOptionsCallback { get { throw null; } set { } }
75+
public System.Collections.Generic.List<System.Net.Security.SslApplicationProtocol> ApplicationProtocols { get { throw null; } set { } }
76+
public System.Func<System.Net.Quic.QuicConnection, System.Net.Security.SslClientHelloInfo, System.Threading.CancellationToken, System.Threading.Tasks.ValueTask<System.Net.Quic.QuicServerConnectionOptions>> ConnectionOptionsCallback { get { throw null; } set { } }
7977
public int ListenBacklog { get { throw null; } set { } }
80-
public required System.Net.IPEndPoint ListenEndPoint { get { throw null; } set { } }
78+
public System.Net.IPEndPoint ListenEndPoint { get { throw null; } set { } }
8179
}
8280
public sealed partial class QuicServerConnectionOptions : System.Net.Quic.QuicConnectionOptions
8381
{
8482
public QuicServerConnectionOptions() { }
85-
public required System.Net.Security.SslServerAuthenticationOptions ServerAuthenticationOptions { get { throw null; } set { } }
83+
public System.Net.Security.SslServerAuthenticationOptions ServerAuthenticationOptions { get { throw null; } set { } }
8684
}
8785
public sealed partial class QuicStream : System.IO.Stream
8886
{
@@ -125,4 +123,9 @@ public override void Write(System.ReadOnlySpan<byte> buffer) { }
125123
public override System.Threading.Tasks.ValueTask WriteAsync(System.ReadOnlyMemory<byte> buffer, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
126124
public override void WriteByte(byte value) { }
127125
}
126+
public enum QuicStreamType
127+
{
128+
Unidirectional = 0,
129+
Bidirectional = 1,
130+
}
128131
}

0 commit comments

Comments
 (0)