diff --git a/.github/workflows/ReportDiff.ps1 b/.github/workflows/ReportDiff.ps1
new file mode 100644
index 0000000000..5a49187e74
--- /dev/null
+++ b/.github/workflows/ReportDiff.ps1
@@ -0,0 +1,28 @@
+# Check the code is in sync
+$changed = (select-string "nothing to commit" artifacts\status.txt).count -eq 0
+if (-not $changed) { exit }
+# Check if tracking issue is open/closed
+$Headers = @{ Authorization = 'token {0}' -f $ENV:GITHUB_TOKEN; };
+$result = Invoke-RestMethod -Uri $issue
+if ($result.state -eq "closed") {
+ $json = "{ `"state`": `"open`" }"
+ $result = Invoke-RestMethod -Method PATCH -Headers $Headers -Uri $issue -Body $json
+}
+# Add a comment
+$status = [IO.File]::ReadAllText("artifacts\status.txt")
+$diff = [IO.File]::ReadAllText("artifacts\diff.txt")
+$body = @"
+The shared code is out of sync.
+
+ The Diff
+
+``````
+$status
+$diff
+``````
+
+
+"@
+$json = ConvertTo-Json -InputObject @{ 'body' = $body }
+$issue = $issue + '/comments'
+$result = Invoke-RestMethod -Method POST -Headers $Headers -Uri $issue -Body $json
diff --git a/.github/workflows/runtime-sync.yml b/.github/workflows/runtime-sync.yml
index 66c0cbc192..5b173f5c0e 100644
--- a/.github/workflows/runtime-sync.yml
+++ b/.github/workflows/runtime-sync.yml
@@ -17,14 +17,12 @@ jobs:
uses: actions/checkout@v2.0.0
with:
# Test this script using changes in a fork
- # repository: 'Tratcher/aspnetcore'
repository: 'dotnet/aspnetcore'
path: aspnetcore
- name: Checkout runtime
uses: actions/checkout@v2.0.0
with:
# Test this script using changes in a fork
- # repository: 'Tratcher/runtime'
repository: 'dotnet/runtime'
path: runtime
- name: Copy
@@ -32,9 +30,7 @@ jobs:
working-directory: .\runtime\src\libraries\Common\src\System\Net\Http\aspnetcore\
env:
ASPNETCORE_REPO: d:\a\aspnetcore\aspnetcore\aspnetcore\
- run: |
- dir
- CopyToAspNetCore.cmd
+ run: CopyToAspNetCore.cmd
- name: Diff
shell: cmd
working-directory: .\aspnetcore\
@@ -51,34 +47,6 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- # Check the code is in sync
- $changed = (select-string "nothing to commit" artifacts\status.txt).count -eq 0
- if (-not $changed) { exit }
# Test this script using an issue in the local forked repo
- # $issue = 'https://api.github.com/repos/Tratcher/aspnetcore/issues/1'
$issue = 'https://api.github.com/repos/dotnet/aspnetcore/issues/18943'
- # Check if tracking issue is open/closed
- $Headers = @{ Authorization = 'token {0}' -f $ENV:GITHUB_TOKEN; };
- $result = Invoke-RestMethod -Uri $issue
- if ($result.state -eq "closed") {
- $json = "{ `"state`": `"open`" }"
- $result = Invoke-RestMethod -Method PATCH -Headers $Headers -Uri $issue -Body $json
- }
- # Add a comment
- $status = [IO.File]::ReadAllText("artifacts\status.txt")
- $diff = [IO.File]::ReadAllText("artifacts\diff.txt")
- $body = @"
- The shared code is out of sync.
-
- The Diff
-
- ``````
- $status
- $diff
- ``````
-
-
- "@
- $json = ConvertTo-Json -InputObject @{ 'body' = $body }
- $issue = $issue + '/comments'
- $result = Invoke-RestMethod -Method POST -Headers $Headers -Uri $issue -Body $json
+ .\aspnetcore\.github\workflows\ReportDiff.ps1
\ No newline at end of file
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 596641a2df..6199b273c2 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -29,33 +29,33 @@
https://github.com/dotnet/aspnetcore-tooling
9acda9485be8e2238067508474dfa44fcc34f81e
-
+
https://github.com/dotnet/efcore
- e3f6eee19b00800855bd2587ebe79f270968fc82
+ 8f730925a179d31311e1f4dc22bb8d5015488401
-
+
https://github.com/dotnet/efcore
- e3f6eee19b00800855bd2587ebe79f270968fc82
+ 8f730925a179d31311e1f4dc22bb8d5015488401
-
+
https://github.com/dotnet/efcore
- e3f6eee19b00800855bd2587ebe79f270968fc82
+ 8f730925a179d31311e1f4dc22bb8d5015488401
-
+
https://github.com/dotnet/efcore
- e3f6eee19b00800855bd2587ebe79f270968fc82
+ 8f730925a179d31311e1f4dc22bb8d5015488401
-
+
https://github.com/dotnet/efcore
- e3f6eee19b00800855bd2587ebe79f270968fc82
+ 8f730925a179d31311e1f4dc22bb8d5015488401
-
+
https://github.com/dotnet/efcore
- e3f6eee19b00800855bd2587ebe79f270968fc82
+ 8f730925a179d31311e1f4dc22bb8d5015488401
-
+
https://github.com/dotnet/efcore
- e3f6eee19b00800855bd2587ebe79f270968fc82
+ 8f730925a179d31311e1f4dc22bb8d5015488401
https://github.com/dotnet/extensions
diff --git a/eng/Versions.props b/eng/Versions.props
index ba22093049..dada3f6192 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -139,13 +139,13 @@
5.0.0-preview.1.20114.1
5.0.0-preview.1.20114.1
- 5.0.0-preview.1.20113.3
- 5.0.0-preview.1.20113.3
- 5.0.0-preview.1.20113.3
- 5.0.0-preview.1.20113.3
- 5.0.0-preview.1.20113.3
- 5.0.0-preview.1.20113.3
- 5.0.0-preview.1.20113.3
+ 5.0.0-preview.2.20120.6
+ 5.0.0-preview.2.20120.6
+ 5.0.0-preview.2.20120.6
+ 5.0.0-preview.2.20120.6
+ 5.0.0-preview.2.20120.6
+ 5.0.0-preview.2.20120.6
+ 5.0.0-preview.2.20120.6
5.0.0-preview.1.20114.5
5.0.0-preview.1.20114.5
diff --git a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
index 61db948363..72a38463da 100644
--- a/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
+++ b/src/Servers/Kestrel/Core/src/Microsoft.AspNetCore.Server.Kestrel.Core.csproj
@@ -1,4 +1,4 @@
-
+
Core components of ASP.NET Core Kestrel cross-platform web server.
@@ -16,7 +16,9 @@
-
+
+
+
diff --git a/src/Servers/Kestrel/Transport.Quic/src/Libraries.cs b/src/Servers/Kestrel/Transport.Quic/src/Libraries.cs
new file mode 100644
index 0000000000..82340cf941
--- /dev/null
+++ b/src/Servers/Kestrel/Transport.Quic/src/Libraries.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+internal static partial class Interop
+{
+ internal static class Libraries
+ {
+ // Compare to https://github.com/dotnet/runtime/blob/63c88901df460c47eaffc6b970c4b5f0aeaf0a88/src/libraries/Common/src/Interop/Linux/Interop.Libraries.cs#L10
+ internal const string MsQuic = "msquic";
+ }
+}
diff --git a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj
index a530b892ee..19b9c38761 100644
--- a/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj
+++ b/src/Servers/Kestrel/Transport.Quic/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.csproj
@@ -1,12 +1,12 @@
-
+
- Libuv transport for the ASP.NET Core Kestrel cross-platform web server.
+ Quic transport for the ASP.NET Core Kestrel cross-platform web server.
$(DefaultNetCoreTargetFramework)
true
aspnetcore;kestrel
true
- CS1591;$(NoWarn)
+ CS1591;CS0436;$(NoWarn)
false
@@ -14,9 +14,13 @@
+
+
+
+
@@ -26,4 +30,11 @@
+
+
+ System.Net.Quic.SR
+
+
+
+
diff --git a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
index 98fb416523..997047f2c6 100644
--- a/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
+++ b/src/Shared/runtime/Http2/Hpack/HPackDecoder.cs
@@ -12,7 +12,7 @@ namespace System.Net.Http.HPack
{
internal class HPackDecoder
{
- private enum State
+ private enum State : byte
{
Ready,
HeaderFieldIndex,
diff --git a/src/Shared/runtime/Http2/Hpack/HeaderField.cs b/src/Shared/runtime/Http2/Hpack/HeaderField.cs
index 1eba82412d..f876204671 100644
--- a/src/Shared/runtime/Http2/Hpack/HeaderField.cs
+++ b/src/Shared/runtime/Http2/Hpack/HeaderField.cs
@@ -21,11 +21,8 @@ namespace System.Net.Http.HPack
// We should revisit our allocation strategy here so we don't need to allocate per entry
// and we have a cap to how much allocation can happen per dynamic table
// (without limiting the number of table entries a server can provide within the table size limit).
- Name = new byte[name.Length];
- name.CopyTo(Name);
-
- Value = new byte[value.Length];
- value.CopyTo(Value);
+ Name = name.ToArray();
+ Value = value.ToArray();
}
public byte[] Name { get; }
diff --git a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
index 6f63d66ce9..958dfac303 100644
--- a/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
+++ b/src/Shared/runtime/Http3/QPack/QPackDecoder.cs
@@ -269,6 +269,10 @@ namespace System.Net.Http.QPack
if (_integerDecoder.BeginTryDecode((byte)prefixInt, LiteralHeaderFieldWithoutNameReferencePrefix, out intResult))
{
+ if (intResult == 0)
+ {
+ throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, ""));
+ }
OnStringLength(intResult, State.HeaderName);
}
else
@@ -303,6 +307,10 @@ namespace System.Net.Http.QPack
case State.HeaderNameLength:
if (_integerDecoder.TryDecode(b, out intResult))
{
+ if (intResult == 0)
+ {
+ throw new QPackDecodingException(SR.Format(SR.net_http_invalid_header_name, ""));
+ }
OnStringLength(intResult, nextState: State.HeaderName);
}
break;
diff --git a/src/Shared/runtime/NetEventSource.Common.cs b/src/Shared/runtime/NetEventSource.Common.cs
new file mode 100644
index 0000000000..46cd2ee685
--- /dev/null
+++ b/src/Shared/runtime/NetEventSource.Common.cs
@@ -0,0 +1,738 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if DEBUG
+// Uncomment to enable runtime checks to help validate that NetEventSource isn't being misused
+// in a way that will cause performance problems, e.g. unexpected boxing of value types.
+//#define DEBUG_NETEVENTSOURCE_MISUSE
+#endif
+
+#nullable enable
+using System.Collections;
+using System.Diagnostics;
+using System.Diagnostics.Tracing;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+#if NET46
+using System.Security;
+#endif
+
+#pragma warning disable CA1823 // not all IDs are used by all partial providers
+
+namespace System.Net
+{
+ // Implementation:
+ // This partial file is meant to be consumed into each System.Net.* assembly that needs to log. Each such assembly also provides
+ // its own NetEventSource partial class that adds an appropriate [EventSource] attribute, giving it a unique name for that assembly.
+ // Those partials can then also add additional events if needed, starting numbering from the NextAvailableEventId defined by this partial.
+
+ // Usage:
+ // - Operations that may allocate (e.g. boxing a value type, using string interpolation, etc.) or that may have computations
+ // at call sites should guard access like:
+ // if (NetEventSource.IsEnabled) NetEventSource.Enter(this, refArg1, valueTypeArg2); // entering an instance method with a value type arg
+ // if (NetEventSource.IsEnabled) NetEventSource.Info(null, $"Found certificate: {cert}"); // info logging with a formattable string
+ // - Operations that have zero allocations / measurable computations at call sites can use a simpler pattern, calling methods like:
+ // NetEventSource.Enter(this); // entering an instance method
+ // NetEventSource.Info(this, "literal string"); // arbitrary message with a literal string
+ // NetEventSource.Enter(this, refArg1, regArg2); // entering an instance method with two reference type arguments
+ // NetEventSource.Enter(null); // entering a static method
+ // NetEventSource.Enter(null, refArg1); // entering a static method with one reference type argument
+ // Debug.Asserts inside the logging methods will help to flag some misuse if the DEBUG_NETEVENTSOURCE_MISUSE compilation constant is defined.
+ // However, because it can be difficult by observation to understand all of the costs involved, guarding can be done everywhere.
+ // - NetEventSource.Fail calls typically do not need to be prefixed with an IsEnabled check, even if they allocate, as FailMessage
+ // should only be used in cases similar to Debug.Fail, where they are not expected to happen in retail builds, and thus extra costs
+ // don't matter.
+ // - Messages can be strings, formattable strings, or any other object. Objects (including those used in formattable strings) have special
+ // formatting applied, controlled by the Format method. Partial specializations can also override this formatting by implementing a partial
+ // method that takes an object and optionally provides a string representation of it, in case a particular library wants to customize further.
+
+ /// Provides logging facilities for System.Net libraries.
+#if NET46
+ [SecuritySafeCritical]
+#endif
+ internal sealed partial class NetEventSource : EventSource
+ {
+ /// The single event source instance to use for all logging.
+ public static readonly NetEventSource Log = new NetEventSource();
+
+ #region Metadata
+ public class Keywords
+ {
+ public const EventKeywords Default = (EventKeywords)0x0001;
+ public const EventKeywords Debug = (EventKeywords)0x0002;
+ public const EventKeywords EnterExit = (EventKeywords)0x0004;
+ }
+
+ private const string MissingMember = "(?)";
+ private const string NullInstance = "(null)";
+ private const string StaticMethodObject = "(static)";
+ private const string NoParameters = "";
+ private const int MaxDumpSize = 1024;
+
+ private const int EnterEventId = 1;
+ private const int ExitEventId = 2;
+ private const int AssociateEventId = 3;
+ private const int InfoEventId = 4;
+ private const int ErrorEventId = 5;
+ private const int CriticalFailureEventId = 6;
+ private const int DumpArrayEventId = 7;
+
+ // These events are implemented in NetEventSource.Security.cs.
+ // Define the ids here so that projects that include NetEventSource.Security.cs will not have conflicts.
+ private const int EnumerateSecurityPackagesId = 8;
+ private const int SspiPackageNotFoundId = 9;
+ private const int AcquireDefaultCredentialId = 10;
+ private const int AcquireCredentialsHandleId = 11;
+ private const int InitializeSecurityContextId = 12;
+ private const int SecurityContextInputBufferId = 13;
+ private const int SecurityContextInputBuffersId = 14;
+ private const int AcceptSecuritContextId = 15;
+ private const int OperationReturnedSomethingId = 16;
+
+ private const int NextAvailableEventId = 17; // Update this value whenever new events are added. Derived types should base all events off of this to avoid conflicts.
+ #endregion
+
+ #region Events
+ #region Enter
+ /// Logs entrance to a method.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// A description of the entrance, including any arguments to the call.
+ /// The calling member.
+ [NonEvent]
+ public static void Enter(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(formattableString);
+ if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters);
+ }
+
+ /// Logs entrance to a method.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The object to log.
+ /// The calling member.
+ [NonEvent]
+ public static void Enter(object? thisOrContextObject, object arg0, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(arg0);
+ if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, $"({Format(arg0)})");
+ }
+
+ /// Logs entrance to a method.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The first object to log.
+ /// The second object to log.
+ /// The calling member.
+ [NonEvent]
+ public static void Enter(object? thisOrContextObject, object arg0, object arg1, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(arg0);
+ DebugValidateArg(arg1);
+ if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, $"({Format(arg0)}, {Format(arg1)})");
+ }
+
+ /// Logs entrance to a method.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The first object to log.
+ /// The second object to log.
+ /// The third object to log.
+ /// The calling member.
+ [NonEvent]
+ public static void Enter(object? thisOrContextObject, object arg0, object arg1, object arg2, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(arg0);
+ DebugValidateArg(arg1);
+ DebugValidateArg(arg2);
+ if (IsEnabled) Log.Enter(IdOf(thisOrContextObject), memberName, $"({Format(arg0)}, {Format(arg1)}, {Format(arg2)})");
+ }
+
+ [Event(EnterEventId, Level = EventLevel.Informational, Keywords = Keywords.EnterExit)]
+ private void Enter(string thisOrContextObject, string? memberName, string parameters) =>
+ WriteEvent(EnterEventId, thisOrContextObject, memberName ?? MissingMember, parameters);
+ #endregion
+
+ #region Exit
+ /// Logs exit from a method.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// A description of the exit operation, including any return values.
+ /// The calling member.
+ [NonEvent]
+ public static void Exit(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(formattableString);
+ if (IsEnabled) Log.Exit(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters);
+ }
+
+ /// Logs exit from a method.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// A return value from the member.
+ /// The calling member.
+ [NonEvent]
+ public static void Exit(object? thisOrContextObject, object arg0, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(arg0);
+ if (IsEnabled) Log.Exit(IdOf(thisOrContextObject), memberName, Format(arg0).ToString());
+ }
+
+ /// Logs exit from a method.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// A return value from the member.
+ /// A second return value from the member.
+ /// The calling member.
+ [NonEvent]
+ public static void Exit(object? thisOrContextObject, object arg0, object arg1, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(arg0);
+ DebugValidateArg(arg1);
+ if (IsEnabled) Log.Exit(IdOf(thisOrContextObject), memberName, $"{Format(arg0)}, {Format(arg1)}");
+ }
+
+ [Event(ExitEventId, Level = EventLevel.Informational, Keywords = Keywords.EnterExit)]
+ private void Exit(string thisOrContextObject, string? memberName, string? result) =>
+ WriteEvent(ExitEventId, thisOrContextObject, memberName ?? MissingMember, result);
+ #endregion
+
+ #region Info
+ /// Logs an information message.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The message to be logged.
+ /// The calling member.
+ [NonEvent]
+ public static void Info(object? thisOrContextObject, FormattableString? formattableString = null, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(formattableString);
+ if (IsEnabled) Log.Info(IdOf(thisOrContextObject), memberName, formattableString != null ? Format(formattableString) : NoParameters);
+ }
+
+ /// Logs an information message.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The message to be logged.
+ /// The calling member.
+ [NonEvent]
+ public static void Info(object? thisOrContextObject, object? message, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(message);
+ if (IsEnabled) Log.Info(IdOf(thisOrContextObject), memberName, Format(message).ToString());
+ }
+
+ [Event(InfoEventId, Level = EventLevel.Informational, Keywords = Keywords.Default)]
+ private void Info(string thisOrContextObject, string? memberName, string? message) =>
+ WriteEvent(InfoEventId, thisOrContextObject, memberName ?? MissingMember, message);
+ #endregion
+
+ #region Error
+ /// Logs an error message.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The message to be logged.
+ /// The calling member.
+ [NonEvent]
+ public static void Error(object? thisOrContextObject, FormattableString formattableString, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(formattableString);
+ if (IsEnabled) Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(formattableString));
+ }
+
+ /// Logs an error message.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The message to be logged.
+ /// The calling member.
+ [NonEvent]
+ public static void Error(object? thisOrContextObject, object message, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(message);
+ if (IsEnabled) Log.ErrorMessage(IdOf(thisOrContextObject), memberName, Format(message).ToString());
+ }
+
+ [Event(ErrorEventId, Level = EventLevel.Error, Keywords = Keywords.Default)]
+ private void ErrorMessage(string thisOrContextObject, string? memberName, string? message) =>
+ WriteEvent(ErrorEventId, thisOrContextObject, memberName ?? MissingMember, message);
+ #endregion
+
+ #region Fail
+ /// Logs a fatal error and raises an assert.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The message to be logged.
+ /// The calling member.
+ [NonEvent]
+ public static void Fail(object? thisOrContextObject, FormattableString formattableString, [CallerMemberName] string? memberName = null)
+ {
+ // Don't call DebugValidateArg on args, as we expect Fail to be used in assert/failure situations
+ // that should never happen in production, and thus we don't care about extra costs.
+
+ if (IsEnabled) Log.CriticalFailure(IdOf(thisOrContextObject), memberName, Format(formattableString));
+ Debug.Fail(Format(formattableString), $"{IdOf(thisOrContextObject)}.{memberName}");
+ }
+
+ /// Logs a fatal error and raises an assert.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The message to be logged.
+ /// The calling member.
+ [NonEvent]
+ public static void Fail(object? thisOrContextObject, object message, [CallerMemberName] string? memberName = null)
+ {
+ // Don't call DebugValidateArg on args, as we expect Fail to be used in assert/failure situations
+ // that should never happen in production, and thus we don't care about extra costs.
+
+ if (IsEnabled) Log.CriticalFailure(IdOf(thisOrContextObject), memberName, Format(message).ToString());
+ Debug.Fail(Format(message).ToString(), $"{IdOf(thisOrContextObject)}.{memberName}");
+ }
+
+ [Event(CriticalFailureEventId, Level = EventLevel.Critical, Keywords = Keywords.Debug)]
+ private void CriticalFailure(string thisOrContextObject, string? memberName, string? message) =>
+ WriteEvent(CriticalFailureEventId, thisOrContextObject, memberName ?? MissingMember, message);
+ #endregion
+
+ #region DumpBuffer
+ /// Logs the contents of a buffer.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The buffer to be logged.
+ /// The calling member.
+ [NonEvent]
+ public static void DumpBuffer(object? thisOrContextObject, byte[] buffer, [CallerMemberName] string? memberName = null)
+ {
+ DumpBuffer(thisOrContextObject, buffer, 0, buffer.Length, memberName);
+ }
+
+ /// Logs the contents of a buffer.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The buffer to be logged.
+ /// The starting offset from which to log.
+ /// The number of bytes to log.
+ /// The calling member.
+ [NonEvent]
+ public static void DumpBuffer(object? thisOrContextObject, byte[] buffer, int offset, int count, [CallerMemberName] string? memberName = null)
+ {
+ if (IsEnabled)
+ {
+ if (offset < 0 || offset > buffer.Length - count)
+ {
+ Fail(thisOrContextObject, $"Invalid {nameof(DumpBuffer)} Args. Length={buffer.Length}, Offset={offset}, Count={count}", memberName);
+ return;
+ }
+
+ count = Math.Min(count, MaxDumpSize);
+
+ byte[] slice = buffer;
+ if (offset != 0 || count != buffer.Length)
+ {
+ slice = new byte[count];
+ Buffer.BlockCopy(buffer, offset, slice, 0, count);
+ }
+
+ Log.DumpBuffer(IdOf(thisOrContextObject), memberName, slice);
+ }
+ }
+
+ /// Logs the contents of a buffer.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The starting location of the buffer to be logged.
+ /// The number of bytes to log.
+ /// The calling member.
+ [NonEvent]
+ public static unsafe void DumpBuffer(object? thisOrContextObject, IntPtr bufferPtr, int count, [CallerMemberName] string? memberName = null)
+ {
+ Debug.Assert(bufferPtr != IntPtr.Zero);
+ Debug.Assert(count >= 0);
+
+ if (IsEnabled)
+ {
+ var buffer = new byte[Math.Min(count, MaxDumpSize)];
+ fixed (byte* targetPtr = buffer)
+ {
+ Buffer.MemoryCopy((byte*)bufferPtr, targetPtr, buffer.Length, buffer.Length);
+ }
+ Log.DumpBuffer(IdOf(thisOrContextObject), memberName, buffer);
+ }
+ }
+
+ [Event(DumpArrayEventId, Level = EventLevel.Verbose, Keywords = Keywords.Debug)]
+ private unsafe void DumpBuffer(string thisOrContextObject, string? memberName, byte[] buffer) =>
+ WriteEvent(DumpArrayEventId, thisOrContextObject, memberName ?? MissingMember, buffer);
+ #endregion
+
+ #region Associate
+ /// Logs a relationship between two objects.
+ /// The first object.
+ /// The second object.
+ /// The calling member.
+ [NonEvent]
+ public static void Associate(object first, object second, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(first);
+ DebugValidateArg(second);
+ if (IsEnabled) Log.Associate(IdOf(first), memberName, IdOf(first), IdOf(second));
+ }
+
+ /// Logs a relationship between two objects.
+ /// `this`, or another object that serves to provide context for the operation.
+ /// The first object.
+ /// The second object.
+ /// The calling member.
+ [NonEvent]
+ public static void Associate(object? thisOrContextObject, object first, object second, [CallerMemberName] string? memberName = null)
+ {
+ DebugValidateArg(thisOrContextObject);
+ DebugValidateArg(first);
+ DebugValidateArg(second);
+ if (IsEnabled) Log.Associate(IdOf(thisOrContextObject), memberName, IdOf(first), IdOf(second));
+ }
+
+ [Event(AssociateEventId, Level = EventLevel.Informational, Keywords = Keywords.Default, Message = "[{2}]<-->[{3}]")]
+ private void Associate(string thisOrContextObject, string? memberName, string first, string second) =>
+ WriteEvent(AssociateEventId, thisOrContextObject, memberName ?? MissingMember, first, second);
+ #endregion
+ #endregion
+
+ #region Helpers
+ [Conditional("DEBUG_NETEVENTSOURCE_MISUSE")]
+ private static void DebugValidateArg(object? arg)
+ {
+ if (!IsEnabled)
+ {
+ Debug.Assert(!(arg is ValueType), $"Should not be passing value type {arg?.GetType()} to logging without IsEnabled check");
+ Debug.Assert(!(arg is FormattableString), $"Should not be formatting FormattableString \"{arg}\" if tracing isn't enabled");
+ }
+ }
+
+ [Conditional("DEBUG_NETEVENTSOURCE_MISUSE")]
+ private static void DebugValidateArg(FormattableString? arg)
+ {
+ Debug.Assert(IsEnabled || arg == null, $"Should not be formatting FormattableString \"{arg}\" if tracing isn't enabled");
+ }
+
+ public static new bool IsEnabled =>
+ Log.IsEnabled();
+
+ [NonEvent]
+ public static string IdOf(object? value) => value != null ? value.GetType().Name + "#" + GetHashCode(value) : NullInstance;
+
+ [NonEvent]
+ public static int GetHashCode(object value) => value?.GetHashCode() ?? 0;
+
+ [NonEvent]
+ public static object Format(object? value)
+ {
+ // If it's null, return a known string for null values
+ if (value == null)
+ {
+ return NullInstance;
+ }
+
+ // Give another partial implementation a chance to provide its own string representation
+ string? result = null;
+ AdditionalCustomizedToString(value, ref result);
+ if (result != null)
+ {
+ return result;
+ }
+
+ // Format arrays with their element type name and length
+ if (value is Array arr)
+ {
+ return $"{arr.GetType().GetElementType()}[{((Array)value).Length}]";
+ }
+
+ // Format ICollections as the name and count
+ if (value is ICollection c)
+ {
+ return $"{c.GetType().Name}({c.Count})";
+ }
+
+ // Format SafeHandles as their type, hash code, and pointer value
+ if (value is SafeHandle handle)
+ {
+ return $"{handle.GetType().Name}:{handle.GetHashCode()}(0x{handle.DangerousGetHandle():X})";
+ }
+
+ // Format IntPtrs as hex
+ if (value is IntPtr)
+ {
+ return $"0x{value:X}";
+ }
+
+ // If the string representation of the instance would just be its type name,
+ // use its id instead.
+ string? toString = value.ToString();
+ if (toString == null || toString == value.GetType().FullName)
+ {
+ return IdOf(value);
+ }
+
+ // Otherwise, return the original object so that the caller does default formatting.
+ return value;
+ }
+
+ [NonEvent]
+ private static string Format(FormattableString s)
+ {
+ switch (s.ArgumentCount)
+ {
+ case 0: return s.Format;
+ case 1: return string.Format(s.Format, Format(s.GetArgument(0)));
+ case 2: return string.Format(s.Format, Format(s.GetArgument(0)), Format(s.GetArgument(1)));
+ case 3: return string.Format(s.Format, Format(s.GetArgument(0)), Format(s.GetArgument(1)), Format(s.GetArgument(2)));
+ default:
+ object?[] args = s.GetArguments();
+ object[] formattedArgs = new object[args.Length];
+ for (int i = 0; i < args.Length; i++)
+ {
+ formattedArgs[i] = Format(args[i]);
+ }
+ return string.Format(s.Format, formattedArgs);
+ }
+ }
+
+ static partial void AdditionalCustomizedToString(T value, ref string? result);
+ #endregion
+
+ #region Custom WriteEvent overloads
+
+ [NonEvent]
+ private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3, string? arg4)
+ {
+ if (IsEnabled())
+ {
+ if (arg1 == null) arg1 = "";
+ if (arg2 == null) arg2 = "";
+ if (arg3 == null) arg3 = "";
+ if (arg4 == null) arg4 = "";
+
+ fixed (char* string1Bytes = arg1)
+ fixed (char* string2Bytes = arg2)
+ fixed (char* string3Bytes = arg3)
+ fixed (char* string4Bytes = arg4)
+ {
+ const int NumEventDatas = 4;
+ var descrs = stackalloc EventData[NumEventDatas];
+
+ descrs[0] = new EventData
+ {
+ DataPointer = (IntPtr)string1Bytes,
+ Size = ((arg1.Length + 1) * 2)
+ };
+ descrs[1] = new EventData
+ {
+ DataPointer = (IntPtr)string2Bytes,
+ Size = ((arg2.Length + 1) * 2)
+ };
+ descrs[2] = new EventData
+ {
+ DataPointer = (IntPtr)string3Bytes,
+ Size = ((arg3.Length + 1) * 2)
+ };
+ descrs[3] = new EventData
+ {
+ DataPointer = (IntPtr)string4Bytes,
+ Size = ((arg4.Length + 1) * 2)
+ };
+
+ WriteEventCore(eventId, NumEventDatas, descrs);
+ }
+ }
+ }
+
+ [NonEvent]
+ private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, byte[]? arg3)
+ {
+ if (IsEnabled())
+ {
+ if (arg1 == null) arg1 = "";
+ if (arg2 == null) arg2 = "";
+ if (arg3 == null) arg3 = Array.Empty();
+
+ fixed (char* arg1Ptr = arg1)
+ fixed (char* arg2Ptr = arg2)
+ fixed (byte* arg3Ptr = arg3)
+ {
+ int bufferLength = arg3.Length;
+ const int NumEventDatas = 4;
+ var descrs = stackalloc EventData[NumEventDatas];
+
+ descrs[0] = new EventData
+ {
+ DataPointer = (IntPtr)arg1Ptr,
+ Size = (arg1.Length + 1) * sizeof(char)
+ };
+ descrs[1] = new EventData
+ {
+ DataPointer = (IntPtr)arg2Ptr,
+ Size = (arg2.Length + 1) * sizeof(char)
+ };
+ descrs[2] = new EventData
+ {
+ DataPointer = (IntPtr)(&bufferLength),
+ Size = 4
+ };
+ descrs[3] = new EventData
+ {
+ DataPointer = (IntPtr)arg3Ptr,
+ Size = bufferLength
+ };
+
+ WriteEventCore(eventId, NumEventDatas, descrs);
+ }
+ }
+ }
+
+ [NonEvent]
+ private unsafe void WriteEvent(int eventId, string? arg1, int arg2, int arg3, int arg4)
+ {
+ if (IsEnabled())
+ {
+ if (arg1 == null) arg1 = "";
+
+ fixed (char* arg1Ptr = arg1)
+ {
+ const int NumEventDatas = 4;
+ var descrs = stackalloc EventData[NumEventDatas];
+
+ descrs[0] = new EventData
+ {
+ DataPointer = (IntPtr)(arg1Ptr),
+ Size = (arg1.Length + 1) * sizeof(char)
+ };
+ descrs[1] = new EventData
+ {
+ DataPointer = (IntPtr)(&arg2),
+ Size = sizeof(int)
+ };
+ descrs[2] = new EventData
+ {
+ DataPointer = (IntPtr)(&arg3),
+ Size = sizeof(int)
+ };
+ descrs[3] = new EventData
+ {
+ DataPointer = (IntPtr)(&arg4),
+ Size = sizeof(int)
+ };
+
+ WriteEventCore(eventId, NumEventDatas, descrs);
+ }
+ }
+ }
+
+ [NonEvent]
+ private unsafe void WriteEvent(int eventId, string? arg1, int arg2, string? arg3)
+ {
+ if (IsEnabled())
+ {
+ if (arg1 == null) arg1 = "";
+ if (arg3 == null) arg3 = "";
+
+ fixed (char* arg1Ptr = arg1)
+ fixed (char* arg3Ptr = arg3)
+ {
+ const int NumEventDatas = 3;
+ var descrs = stackalloc EventData[NumEventDatas];
+
+ descrs[0] = new EventData
+ {
+ DataPointer = (IntPtr)(arg1Ptr),
+ Size = (arg1.Length + 1) * sizeof(char)
+ };
+ descrs[1] = new EventData
+ {
+ DataPointer = (IntPtr)(&arg2),
+ Size = sizeof(int)
+ };
+ descrs[2] = new EventData
+ {
+ DataPointer = (IntPtr)(arg3Ptr),
+ Size = (arg3.Length + 1) * sizeof(char)
+ };
+
+ WriteEventCore(eventId, NumEventDatas, descrs);
+ }
+ }
+ }
+
+ [NonEvent]
+ private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, int arg3)
+ {
+ if (IsEnabled())
+ {
+ if (arg1 == null) arg1 = "";
+ if (arg2 == null) arg2 = "";
+
+ fixed (char* arg1Ptr = arg1)
+ fixed (char* arg2Ptr = arg2)
+ {
+ const int NumEventDatas = 3;
+ var descrs = stackalloc EventData[NumEventDatas];
+
+ descrs[0] = new EventData
+ {
+ DataPointer = (IntPtr)(arg1Ptr),
+ Size = (arg1.Length + 1) * sizeof(char)
+ };
+ descrs[1] = new EventData
+ {
+ DataPointer = (IntPtr)(arg2Ptr),
+ Size = (arg2.Length + 1) * sizeof(char)
+ };
+ descrs[2] = new EventData
+ {
+ DataPointer = (IntPtr)(&arg3),
+ Size = sizeof(int)
+ };
+
+ WriteEventCore(eventId, NumEventDatas, descrs);
+ }
+ }
+ }
+
+ [NonEvent]
+ private unsafe void WriteEvent(int eventId, string? arg1, string? arg2, string? arg3, int arg4)
+ {
+ if (IsEnabled())
+ {
+ if (arg1 == null) arg1 = "";
+ if (arg2 == null) arg2 = "";
+ if (arg3 == null) arg3 = "";
+
+ fixed (char* arg1Ptr = arg1)
+ fixed (char* arg2Ptr = arg2)
+ fixed (char* arg3Ptr = arg3)
+ {
+ const int NumEventDatas = 4;
+ var descrs = stackalloc EventData[NumEventDatas];
+
+ descrs[0] = new EventData
+ {
+ DataPointer = (IntPtr)(arg1Ptr),
+ Size = (arg1.Length + 1) * sizeof(char)
+ };
+ descrs[1] = new EventData
+ {
+ DataPointer = (IntPtr)(arg2Ptr),
+ Size = (arg2.Length + 1) * sizeof(char)
+ };
+ descrs[2] = new EventData
+ {
+ DataPointer = (IntPtr)(arg3Ptr),
+ Size = (arg3.Length + 1) * sizeof(char)
+ };
+ descrs[3] = new EventData
+ {
+ DataPointer = (IntPtr)(&arg4),
+ Size = sizeof(int)
+ };
+
+ WriteEventCore(eventId, NumEventDatas, descrs);
+ }
+ }
+ }
+ #endregion
+ }
+}
diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs
new file mode 100644
index 0000000000..d9ab07022e
--- /dev/null
+++ b/src/Shared/runtime/Quic/Implementations/Mock/MockConnection.cs
@@ -0,0 +1,226 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers.Binary;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+ internal sealed class MockConnection : QuicConnectionProvider
+ {
+ private readonly bool _isClient;
+ private bool _disposed = false;
+ private IPEndPoint _remoteEndPoint;
+ private IPEndPoint _localEndPoint;
+ private object _syncObject = new object();
+ private Socket _socket = null;
+ private IPEndPoint _peerListenEndPoint = null;
+ private TcpListener _inboundListener = null;
+ private long _nextOutboundBidirectionalStream;
+ private long _nextOutboundUnidirectionalStream;
+
+ // Constructor for outbound connections
+ internal MockConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null)
+ {
+ _remoteEndPoint = remoteEndPoint;
+ _localEndPoint = localEndPoint;
+
+ _isClient = true;
+ _nextOutboundBidirectionalStream = 0;
+ _nextOutboundUnidirectionalStream = 2;
+ }
+
+ // Constructor for accepted inbound connections
+ internal MockConnection(Socket socket, IPEndPoint peerListenEndPoint, TcpListener inboundListener)
+ {
+ _isClient = false;
+ _nextOutboundBidirectionalStream = 1;
+ _nextOutboundUnidirectionalStream = 3;
+ _socket = socket;
+ _peerListenEndPoint = peerListenEndPoint;
+ _inboundListener = inboundListener;
+ _localEndPoint = (IPEndPoint)socket.LocalEndPoint;
+ _remoteEndPoint = (IPEndPoint)socket.RemoteEndPoint;
+ }
+
+ internal override bool Connected
+ {
+ get
+ {
+ CheckDisposed();
+
+ return _socket != null;
+ }
+ }
+
+ internal override IPEndPoint LocalEndPoint => new IPEndPoint(_localEndPoint.Address, _localEndPoint.Port);
+
+ internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port);
+
+ internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException();
+
+ internal override async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ if (Connected)
+ {
+ // TODO: Exception text
+ throw new InvalidOperationException("Already connected");
+ }
+
+ Socket socket = new Socket(_remoteEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ await socket.ConnectAsync(_remoteEndPoint).ConfigureAwait(false);
+ socket.NoDelay = true;
+
+ _localEndPoint = (IPEndPoint)socket.LocalEndPoint;
+
+ // Listen on a new local endpoint for inbound streams
+ TcpListener inboundListener = new TcpListener(_localEndPoint.Address, 0);
+ inboundListener.Start();
+ int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port;
+
+ // Write inbound listen port to socket so server can read it
+ byte[] buffer = new byte[4];
+ BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort);
+ await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
+
+ // Read first 4 bytes to get server listen port
+ int bytesRead = 0;
+ do
+ {
+ bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false);
+ } while (bytesRead != buffer.Length);
+
+ int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer);
+ IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint).Address, peerListenPort);
+
+ _socket = socket;
+ _peerListenEndPoint = peerListenEndPoint;
+ _inboundListener = inboundListener;
+ }
+
+ internal override QuicStreamProvider OpenUnidirectionalStream()
+ {
+ long streamId;
+ lock (_syncObject)
+ {
+ streamId = _nextOutboundUnidirectionalStream;
+ _nextOutboundUnidirectionalStream += 4;
+ }
+
+ return new MockStream(this, streamId, bidirectional: false);
+ }
+
+ internal override QuicStreamProvider OpenBidirectionalStream()
+ {
+ long streamId;
+ lock (_syncObject)
+ {
+ streamId = _nextOutboundBidirectionalStream;
+ _nextOutboundBidirectionalStream += 4;
+ }
+
+ return new MockStream(this, streamId, bidirectional: true);
+ }
+
+ internal override long GetRemoteAvailableUnidirectionalStreamCount()
+ {
+ throw new NotImplementedException();
+ }
+
+ internal override long GetRemoteAvailableBidirectionalStreamCount()
+ {
+ throw new NotImplementedException();
+ }
+
+ internal async Task CreateOutboundMockStreamAsync(long streamId)
+ {
+ Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
+ await socket.ConnectAsync(_peerListenEndPoint).ConfigureAwait(false);
+ socket.NoDelay = true;
+
+ // Write stream ID to socket so server can read it
+ byte[] buffer = new byte[8];
+ BinaryPrimitives.WriteInt64LittleEndian(buffer, streamId);
+ await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
+
+ return socket;
+ }
+
+ internal override async ValueTask AcceptStreamAsync(CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ Socket socket = await _inboundListener.AcceptSocketAsync().ConfigureAwait(false);
+
+ // Read first bytes to get stream ID
+ byte[] buffer = new byte[8];
+ int bytesRead = 0;
+ do
+ {
+ bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false);
+ } while (bytesRead != buffer.Length);
+
+ long streamId = BinaryPrimitives.ReadInt64LittleEndian(buffer);
+
+ bool clientInitiated = ((streamId & 0b01) == 0);
+ if (clientInitiated == _isClient)
+ {
+ throw new Exception($"Wrong initiator on accepted stream??? streamId={streamId}, _isClient={_isClient}");
+ }
+
+ bool bidirectional = ((streamId & 0b10) == 0);
+ return new MockStream(socket, streamId, bidirectional: bidirectional);
+ }
+
+ internal override ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default)
+ {
+ Dispose();
+ return default;
+ }
+
+ private void CheckDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(QuicConnection));
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _socket?.Dispose();
+ _socket = null;
+
+ _inboundListener?.Stop();
+ _inboundListener = null;
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // TODO: set large fields to null.
+
+ _disposed = true;
+ }
+ }
+
+ ~MockConnection()
+ {
+ Dispose(false);
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockImplementationProvider.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockImplementationProvider.cs
new file mode 100644
index 0000000000..b70a113284
--- /dev/null
+++ b/src/Shared/runtime/Quic/Implementations/Mock/MockImplementationProvider.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Security;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+ internal sealed class MockImplementationProvider : QuicImplementationProvider
+ {
+ internal override QuicListenerProvider CreateListener(QuicListenerOptions options)
+ {
+ return new MockListener(options.ListenEndPoint, options.ServerAuthenticationOptions);
+ }
+
+ internal override QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options)
+ {
+ return new MockConnection(options.RemoteEndPoint, options.ClientAuthenticationOptions, options.LocalEndPoint);
+ }
+ }
+}
diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs
new file mode 100644
index 0000000000..911f1896b1
--- /dev/null
+++ b/src/Shared/runtime/Quic/Implementations/Mock/MockListener.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Sockets;
+using System.Net.Security;
+using System.Threading.Tasks;
+using System.Threading;
+using System.Buffers.Binary;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+ internal sealed class MockListener : QuicListenerProvider
+ {
+ private bool _disposed = false;
+ private SslServerAuthenticationOptions _sslOptions;
+ private IPEndPoint _listenEndPoint;
+ private TcpListener _tcpListener = null;
+
+ internal MockListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
+ {
+ if (listenEndPoint == null)
+ {
+ throw new ArgumentNullException(nameof(listenEndPoint));
+ }
+
+ _sslOptions = sslServerAuthenticationOptions;
+ _listenEndPoint = listenEndPoint;
+
+ _tcpListener = new TcpListener(listenEndPoint);
+ }
+
+ // IPEndPoint is mutable, so we must create a new instance every time this is retrieved.
+ internal override IPEndPoint ListenEndPoint => new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port);
+
+ internal override async ValueTask AcceptConnectionAsync(CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ Socket socket = await _tcpListener.AcceptSocketAsync().ConfigureAwait(false);
+ socket.NoDelay = true;
+
+ // Read first 4 bytes to get client listen port
+ byte[] buffer = new byte[4];
+ int bytesRead = 0;
+ do
+ {
+ bytesRead += await socket.ReceiveAsync(buffer.AsMemory().Slice(bytesRead), SocketFlags.None).ConfigureAwait(false);
+ } while (bytesRead != buffer.Length);
+
+ int peerListenPort = BinaryPrimitives.ReadInt32LittleEndian(buffer);
+ IPEndPoint peerListenEndPoint = new IPEndPoint(((IPEndPoint)socket.RemoteEndPoint).Address, peerListenPort);
+
+ // Listen on a new local endpoint for inbound streams
+ TcpListener inboundListener = new TcpListener(_listenEndPoint.Address, 0);
+ inboundListener.Start();
+ int inboundListenPort = ((IPEndPoint)inboundListener.LocalEndpoint).Port;
+
+ // Write inbound listen port to socket so client can read it
+ BinaryPrimitives.WriteInt32LittleEndian(buffer, inboundListenPort);
+ await socket.SendAsync(buffer, SocketFlags.None).ConfigureAwait(false);
+
+ return new MockConnection(socket, peerListenEndPoint, inboundListener);
+ }
+
+ internal override void Start()
+ {
+ CheckDisposed();
+
+ _tcpListener.Start();
+
+ if (_listenEndPoint.Port == 0)
+ {
+ // Get auto-assigned port
+ _listenEndPoint = (IPEndPoint)_tcpListener.LocalEndpoint;
+ }
+ }
+
+ internal override void Close()
+ {
+ Dispose();
+ }
+
+ private void CheckDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(QuicListener));
+ }
+ }
+
+ private void Dispose(bool disposing)
+ {
+ if (!_disposed)
+ {
+ if (disposing)
+ {
+ _tcpListener?.Stop();
+ _tcpListener = null;
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // TODO: set large fields to null.
+
+ _disposed = true;
+ }
+ }
+
+ ~MockListener()
+ {
+ Dispose(false);
+ }
+
+ public override void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+ }
+}
diff --git a/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs b/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs
new file mode 100644
index 0000000000..187ba680e1
--- /dev/null
+++ b/src/Shared/runtime/Quic/Implementations/Mock/MockStream.cs
@@ -0,0 +1,259 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Diagnostics;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations.Mock
+{
+ internal sealed class MockStream : QuicStreamProvider
+ {
+ private bool _disposed = false;
+ private readonly long _streamId;
+ private bool _canRead;
+ private bool _canWrite;
+
+ private MockConnection _connection;
+
+ private Socket _socket = null;
+
+ // Constructor for outbound streams
+ internal MockStream(MockConnection connection, long streamId, bool bidirectional)
+ {
+ _connection = connection;
+ _streamId = streamId;
+ _canRead = bidirectional;
+ _canWrite = true;
+ }
+
+ // Constructor for inbound streams
+ internal MockStream(Socket socket, long streamId, bool bidirectional)
+ {
+ _socket = socket;
+ _streamId = streamId;
+ _canRead = true;
+ _canWrite = bidirectional;
+ }
+
+ private async ValueTask ConnectAsync(CancellationToken cancellationToken = default)
+ {
+ Debug.Assert(_connection != null, "Stream not connected but no connection???");
+
+ _socket = await _connection.CreateOutboundMockStreamAsync(_streamId).ConfigureAwait(false);
+
+ // Don't need to hold on to the connection any longer.
+ _connection = null;
+ }
+
+ internal override long StreamId
+ {
+ get
+ {
+ CheckDisposed();
+ return _streamId;
+ }
+ }
+
+ internal override bool CanRead => _canRead;
+
+ internal override int Read(Span buffer)
+ {
+ CheckDisposed();
+
+ if (!_canRead)
+ {
+ throw new NotSupportedException();
+ }
+
+ return _socket.Receive(buffer);
+ }
+
+ internal override async ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ if (!_canRead)
+ {
+ throw new NotSupportedException();
+ }
+
+ if (_socket == null)
+ {
+ await ConnectAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ return await _socket.ReceiveAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ }
+
+ internal override bool CanWrite => _canWrite;
+
+ internal override void Write(ReadOnlySpan buffer)
+ {
+ CheckDisposed();
+
+ if (!_canWrite)
+ {
+ throw new NotSupportedException();
+ }
+
+ _socket.Send(buffer);
+ }
+
+ internal override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
+ {
+ return WriteAsync(buffer, endStream: false, cancellationToken);
+ }
+
+ internal override async ValueTask WriteAsync(ReadOnlyMemory buffer, bool endStream, CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ if (!_canWrite)
+ {
+ throw new NotSupportedException();
+ }
+
+ if (_socket == null)
+ {
+ await ConnectAsync(cancellationToken).ConfigureAwait(false);
+ }
+ await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+
+ if (endStream)
+ {
+ _socket.Shutdown(SocketShutdown.Send);
+ }
+ }
+
+ internal override ValueTask WriteAsync(ReadOnlySequence buffers, CancellationToken cancellationToken = default)
+ {
+ return WriteAsync(buffers, endStream: false, cancellationToken);
+ }
+ internal override async ValueTask WriteAsync(ReadOnlySequence buffers, bool endStream, CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ if (!_canWrite)
+ {
+ throw new NotSupportedException();
+ }
+
+ if (_socket == null)
+ {
+ await ConnectAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ foreach (ReadOnlyMemory buffer in buffers)
+ {
+ await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ }
+
+ if (endStream)
+ {
+ _socket.Shutdown(SocketShutdown.Send);
+ }
+ }
+
+ internal override ValueTask WriteAsync(ReadOnlyMemory> buffers, CancellationToken cancellationToken = default)
+ {
+ return WriteAsync(buffers, endStream: false, cancellationToken);
+ }
+ internal override async ValueTask WriteAsync(ReadOnlyMemory> buffers, bool endStream, CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ if (!_canWrite)
+ {
+ throw new NotSupportedException();
+ }
+
+ if (_socket == null)
+ {
+ await ConnectAsync(cancellationToken).ConfigureAwait(false);
+ }
+
+ foreach (ReadOnlyMemory buffer in buffers.ToArray())
+ {
+ await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
+ }
+
+ if (endStream)
+ {
+ _socket.Shutdown(SocketShutdown.Send);
+ }
+ }
+
+ internal override void Flush()
+ {
+ CheckDisposed();
+ }
+
+ internal override Task FlushAsync(CancellationToken cancellationToken)
+ {
+ CheckDisposed();
+
+ return Task.CompletedTask;
+ }
+
+ internal override void AbortRead(long errorCode)
+ {
+ throw new NotImplementedException();
+ }
+
+ internal override void AbortWrite(long errorCode)
+ {
+ throw new NotImplementedException();
+ }
+
+
+ internal override ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default)
+ {
+ CheckDisposed();
+
+ return default;
+ }
+
+ internal override void Shutdown()
+ {
+ CheckDisposed();
+
+ _socket.Shutdown(SocketShutdown.Send);
+ }
+
+ private void CheckDisposed()
+ {
+ if (_disposed)
+ {
+ throw new ObjectDisposedException(nameof(QuicStream));
+ }
+ }
+
+ public override void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ _socket?.Dispose();
+ _socket = null;
+ }
+ }
+
+ public override ValueTask DisposeAsync()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ _socket?.Dispose();
+ _socket = null;
+ }
+
+ return default;
+ }
+ }
+}
diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs
new file mode 100644
index 0000000000..2ecf0eb210
--- /dev/null
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs
@@ -0,0 +1,85 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Net.Sockets;
+using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal static class MsQuicAddressHelpers
+ {
+ internal const ushort IPv4 = 2;
+ internal const ushort IPv6 = 23;
+
+ internal static unsafe IPEndPoint INetToIPEndPoint(SOCKADDR_INET inetAddress)
+ {
+ if (inetAddress.si_family == IPv4)
+ {
+ return new IPEndPoint(new IPAddress(inetAddress.Ipv4.Address), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv4.sin_port));
+ }
+ else
+ {
+ return new IPEndPoint(new IPAddress(inetAddress.Ipv6.Address), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv6._port));
+ }
+ }
+
+ internal static SOCKADDR_INET IPEndPointToINet(IPEndPoint endpoint)
+ {
+ SOCKADDR_INET socketAddress = default;
+ byte[] buffer = endpoint.Address.GetAddressBytes();
+ if (endpoint.Address != IPAddress.Any && endpoint.Address != IPAddress.IPv6Any)
+ {
+ switch (endpoint.Address.AddressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ socketAddress.Ipv4.sin_addr0 = buffer[0];
+ socketAddress.Ipv4.sin_addr1 = buffer[1];
+ socketAddress.Ipv4.sin_addr2 = buffer[2];
+ socketAddress.Ipv4.sin_addr3 = buffer[3];
+ socketAddress.Ipv4.sin_family = IPv4;
+ break;
+ case AddressFamily.InterNetworkV6:
+ socketAddress.Ipv6._addr0 = buffer[0];
+ socketAddress.Ipv6._addr1 = buffer[1];
+ socketAddress.Ipv6._addr2 = buffer[2];
+ socketAddress.Ipv6._addr3 = buffer[3];
+ socketAddress.Ipv6._addr4 = buffer[4];
+ socketAddress.Ipv6._addr5 = buffer[5];
+ socketAddress.Ipv6._addr6 = buffer[6];
+ socketAddress.Ipv6._addr7 = buffer[7];
+ socketAddress.Ipv6._addr8 = buffer[8];
+ socketAddress.Ipv6._addr9 = buffer[9];
+ socketAddress.Ipv6._addr10 = buffer[10];
+ socketAddress.Ipv6._addr11 = buffer[11];
+ socketAddress.Ipv6._addr12 = buffer[12];
+ socketAddress.Ipv6._addr13 = buffer[13];
+ socketAddress.Ipv6._addr14 = buffer[14];
+ socketAddress.Ipv6._addr15 = buffer[15];
+ socketAddress.Ipv6._family = IPv6;
+ break;
+ default:
+ throw new ArgumentException("Only IPv4 or IPv6 are supported");
+ }
+ }
+
+ SetPort(endpoint.Address.AddressFamily, ref socketAddress, endpoint.Port);
+ return socketAddress;
+ }
+
+ private static void SetPort(AddressFamily addressFamily, ref SOCKADDR_INET socketAddrInet, int originalPort)
+ {
+ ushort convertedPort = (ushort)IPAddress.HostToNetworkOrder((short)originalPort);
+ switch (addressFamily)
+ {
+ case AddressFamily.InterNetwork:
+ socketAddrInet.Ipv4.sin_port = convertedPort;
+ break;
+ case AddressFamily.InterNetworkV6:
+ default:
+ socketAddrInet.Ipv6._port = convertedPort;
+ break;
+ }
+ }
+ }
+}
diff --git a/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
new file mode 100644
index 0000000000..30e5cba6cb
--- /dev/null
+++ b/src/Shared/runtime/Quic/Implementations/MsQuic/Internal/MsQuicApi.cs
@@ -0,0 +1,361 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.IO;
+using System.Net.Security;
+using System.Runtime.InteropServices;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace System.Net.Quic.Implementations.MsQuic.Internal
+{
+ internal class MsQuicApi : IDisposable
+ {
+ private bool _disposed;
+
+ private readonly IntPtr _registrationContext;
+
+ private unsafe MsQuicApi()
+ {
+ MsQuicNativeMethods.NativeApi* registration;
+
+ try
+ {
+ uint status = Interop.MsQuic.MsQuicOpen(version: 1, out registration);
+ if (!MsQuicStatusHelper.SuccessfulStatusCode(status))
+ {
+ throw new NotSupportedException(SR.net_quic_notsupported);
+ }
+ }
+ catch (DllNotFoundException)
+ {
+ throw new NotSupportedException(SR.net_quic_notsupported);
+ }
+
+ MsQuicNativeMethods.NativeApi nativeRegistration = *registration;
+
+ RegistrationOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.RegistrationOpen);
+ RegistrationCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.RegistrationClose);
+
+ SecConfigCreateDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SecConfigCreate);
+ SecConfigDeleteDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SecConfigDelete);
+ SessionOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SessionOpen);
+ SessionCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SessionClose);
+ SessionShutdownDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SessionShutdown);
+
+ ListenerOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ListenerOpen);
+ ListenerCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ListenerClose);
+ ListenerStartDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ListenerStart);
+ ListenerStopDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ListenerStop);
+
+ ConnectionOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ConnectionOpen);
+ ConnectionCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ConnectionClose);
+ ConnectionShutdownDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ConnectionShutdown);
+ ConnectionStartDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.ConnectionStart);
+
+ StreamOpenDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.StreamOpen);
+ StreamCloseDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.StreamClose);
+ StreamStartDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.StreamStart);
+ StreamShutdownDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.StreamShutdown);
+ StreamSendDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.StreamSend);
+ StreamReceiveCompleteDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.StreamReceiveComplete);
+ StreamReceiveSetEnabledDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.StreamReceiveSetEnabled);
+ SetContextDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SetContext);
+ GetContextDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.GetContext);
+ SetCallbackHandlerDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SetCallbackHandler);
+
+ SetParamDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.SetParam);
+ GetParamDelegate =
+ Marshal.GetDelegateForFunctionPointer(
+ nativeRegistration.GetParam);
+
+ RegistrationOpenDelegate(Encoding.UTF8.GetBytes("SystemNetQuic"), out IntPtr ctx);
+ _registrationContext = ctx;
+ }
+
+ internal static MsQuicApi Api { get; }
+
+ internal static bool IsQuicSupported { get; }
+
+ static MsQuicApi()
+ {
+ // MsQuicOpen will succeed even if the platform will not support it. It will then fail with unspecified
+ // platform-specific errors in subsequent callbacks. For now, check for the minimum build we've tested it on.
+
+ // TODO:
+ // - Hopefully, MsQuicOpen will perform this check for us and give us a consistent error code.
+ // - Otherwise, dial this in to reflect actual minimum requirements and add some sort of platform
+ // error code mapping when creating exceptions.
+
+ OperatingSystem ver = Environment.OSVersion;
+
+ if (ver.Platform == PlatformID.Win32NT && ver.Version < new Version(10, 0, 19041, 0))
+ {
+ IsQuicSupported = false;
+ return;
+ }
+
+ // TODO: try to initialize TLS 1.3 in SslStream.
+
+ try
+ {
+ Api = new MsQuicApi();
+ IsQuicSupported = true;
+ }
+ catch (NotSupportedException)
+ {
+ IsQuicSupported = false;
+ }
+ }
+
+ internal MsQuicNativeMethods.RegistrationOpenDelegate RegistrationOpenDelegate { get; }
+ internal MsQuicNativeMethods.RegistrationCloseDelegate RegistrationCloseDelegate { get; }
+
+ internal MsQuicNativeMethods.SecConfigCreateDelegate SecConfigCreateDelegate { get; }
+ internal MsQuicNativeMethods.SecConfigDeleteDelegate SecConfigDeleteDelegate { get; }
+
+ internal MsQuicNativeMethods.SessionOpenDelegate SessionOpenDelegate { get; }
+ internal MsQuicNativeMethods.SessionCloseDelegate SessionCloseDelegate { get; }
+ internal MsQuicNativeMethods.SessionShutdownDelegate SessionShutdownDelegate { get; }
+
+ internal MsQuicNativeMethods.ListenerOpenDelegate ListenerOpenDelegate { get; }
+ internal MsQuicNativeMethods.ListenerCloseDelegate ListenerCloseDelegate { get; }
+ internal MsQuicNativeMethods.ListenerStartDelegate ListenerStartDelegate { get; }
+ internal MsQuicNativeMethods.ListenerStopDelegate ListenerStopDelegate { get; }
+
+ internal MsQuicNativeMethods.ConnectionOpenDelegate ConnectionOpenDelegate { get; }
+ internal MsQuicNativeMethods.ConnectionCloseDelegate ConnectionCloseDelegate { get; }
+ internal MsQuicNativeMethods.ConnectionShutdownDelegate ConnectionShutdownDelegate { get; }
+ internal MsQuicNativeMethods.ConnectionStartDelegate ConnectionStartDelegate { get; }
+
+ internal MsQuicNativeMethods.StreamOpenDelegate StreamOpenDelegate { get; }
+ internal MsQuicNativeMethods.StreamCloseDelegate StreamCloseDelegate { get; }
+ internal MsQuicNativeMethods.StreamStartDelegate StreamStartDelegate { get; }
+ internal MsQuicNativeMethods.StreamShutdownDelegate StreamShutdownDelegate { get; }
+ internal MsQuicNativeMethods.StreamSendDelegate StreamSendDelegate { get; }
+ internal MsQuicNativeMethods.StreamReceiveCompleteDelegate StreamReceiveCompleteDelegate { get; }
+ internal MsQuicNativeMethods.StreamReceiveSetEnabledDelegate StreamReceiveSetEnabledDelegate { get; }
+
+ internal MsQuicNativeMethods.SetContextDelegate SetContextDelegate { get; }
+ internal MsQuicNativeMethods.GetContextDelegate GetContextDelegate { get; }
+ internal MsQuicNativeMethods.SetCallbackHandlerDelegate SetCallbackHandlerDelegate { get; }
+
+ internal MsQuicNativeMethods.SetParamDelegate SetParamDelegate { get; }
+ internal MsQuicNativeMethods.GetParamDelegate GetParamDelegate { get; }
+
+ internal unsafe uint UnsafeSetParam(
+ IntPtr Handle,
+ uint Level,
+ uint Param,
+ MsQuicNativeMethods.QuicBuffer Buffer)
+ {
+ return SetParamDelegate(
+ Handle,
+ Level,
+ Param,
+ Buffer.Length,
+ Buffer.Buffer);
+ }
+
+ internal unsafe uint UnsafeGetParam(
+ IntPtr Handle,
+ uint Level,
+ uint Param,
+ ref MsQuicNativeMethods.QuicBuffer Buffer)
+ {
+ uint bufferLength = Buffer.Length;
+ byte* buf = Buffer.Buffer;
+ return GetParamDelegate(
+ Handle,
+ Level,
+ Param,
+ &bufferLength,
+ buf);
+ }
+
+ public async ValueTask CreateSecurityConfig(X509Certificate certificate, string certFilePath, string privateKeyFilePath)
+ {
+ MsQuicSecurityConfig secConfig = null;
+ var tcs = new TaskCompletionSource
@@ -15,6 +15,9 @@
+
+
+
@@ -56,4 +59,4 @@
-
+
\ No newline at end of file
diff --git a/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj b/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj
index 251681f130..6a1851569e 100644
--- a/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj
+++ b/src/SignalR/clients/java/signalr/signalr.client.java.Tests.javaproj
@@ -11,6 +11,9 @@
true
+
+ true
+
$(GradleOptions) -Dorg.gradle.daemon=false
$(OutputPath)