Merge branch 'master' into johluo/migrating-extensions

This commit is contained in:
John Luo 2020-02-20 18:22:54 -08:00 committed by GitHub
commit 793857f778
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 5476 additions and 68 deletions

28
.github/workflows/ReportDiff.ps1 vendored Normal file
View File

@ -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.
<details>
<summary>The Diff</summary>
``````
$status
$diff
``````
</details>
"@
$json = ConvertTo-Json -InputObject @{ 'body' = $body }
$issue = $issue + '/comments'
$result = Invoke-RestMethod -Method POST -Headers $Headers -Uri $issue -Body $json

View File

@ -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.
<details>
<summary>The Diff</summary>
``````
$status
$diff
``````
</details>
"@
$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

View File

@ -29,33 +29,33 @@
<Uri>https://github.com/dotnet/aspnetcore-tooling</Uri>
<Sha>9acda9485be8e2238067508474dfa44fcc34f81e</Sha>
</Dependency>
<Dependency Name="dotnet-ef" Version="5.0.0-preview.1.20113.3">
<Dependency Name="dotnet-ef" Version="5.0.0-preview.2.20120.6">
<Uri>https://github.com/dotnet/efcore</Uri>
<Sha>e3f6eee19b00800855bd2587ebe79f270968fc82</Sha>
<Sha>8f730925a179d31311e1f4dc22bb8d5015488401</Sha>
</Dependency>
<Dependency Name="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0-preview.1.20113.3">
<Dependency Name="Microsoft.EntityFrameworkCore.InMemory" Version="5.0.0-preview.2.20120.6">
<Uri>https://github.com/dotnet/efcore</Uri>
<Sha>e3f6eee19b00800855bd2587ebe79f270968fc82</Sha>
<Sha>8f730925a179d31311e1f4dc22bb8d5015488401</Sha>
</Dependency>
<Dependency Name="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0-preview.1.20113.3">
<Dependency Name="Microsoft.EntityFrameworkCore.Relational" Version="5.0.0-preview.2.20120.6">
<Uri>https://github.com/dotnet/efcore</Uri>
<Sha>e3f6eee19b00800855bd2587ebe79f270968fc82</Sha>
<Sha>8f730925a179d31311e1f4dc22bb8d5015488401</Sha>
</Dependency>
<Dependency Name="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0-preview.1.20113.3">
<Dependency Name="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.0-preview.2.20120.6">
<Uri>https://github.com/dotnet/efcore</Uri>
<Sha>e3f6eee19b00800855bd2587ebe79f270968fc82</Sha>
<Sha>8f730925a179d31311e1f4dc22bb8d5015488401</Sha>
</Dependency>
<Dependency Name="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0-preview.1.20113.3">
<Dependency Name="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.0-preview.2.20120.6">
<Uri>https://github.com/dotnet/efcore</Uri>
<Sha>e3f6eee19b00800855bd2587ebe79f270968fc82</Sha>
<Sha>8f730925a179d31311e1f4dc22bb8d5015488401</Sha>
</Dependency>
<Dependency Name="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0-preview.1.20113.3">
<Dependency Name="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0-preview.2.20120.6">
<Uri>https://github.com/dotnet/efcore</Uri>
<Sha>e3f6eee19b00800855bd2587ebe79f270968fc82</Sha>
<Sha>8f730925a179d31311e1f4dc22bb8d5015488401</Sha>
</Dependency>
<Dependency Name="Microsoft.EntityFrameworkCore" Version="5.0.0-preview.1.20113.3">
<Dependency Name="Microsoft.EntityFrameworkCore" Version="5.0.0-preview.2.20120.6">
<Uri>https://github.com/dotnet/efcore</Uri>
<Sha>e3f6eee19b00800855bd2587ebe79f270968fc82</Sha>
<Sha>8f730925a179d31311e1f4dc22bb8d5015488401</Sha>
</Dependency>
<Dependency Name="Microsoft.AspNetCore.Analyzer.Testing" Version="5.0.0-preview.1.20114.1" CoherentParentDependency="Microsoft.AspNetCore.Razor.Language">
<Uri>https://github.com/dotnet/extensions</Uri>

View File

@ -139,13 +139,13 @@
<MicrosoftExtensionsPrimitivesPackageVersion>5.0.0-preview.1.20114.1</MicrosoftExtensionsPrimitivesPackageVersion>
<MicrosoftInternalExtensionsRefsPackageVersion>5.0.0-preview.1.20114.1</MicrosoftInternalExtensionsRefsPackageVersion>
<!-- Packages from dotnet/efcore -->
<dotnetefPackageVersion>5.0.0-preview.1.20113.3</dotnetefPackageVersion>
<MicrosoftEntityFrameworkCoreInMemoryPackageVersion>5.0.0-preview.1.20113.3</MicrosoftEntityFrameworkCoreInMemoryPackageVersion>
<MicrosoftEntityFrameworkCoreRelationalPackageVersion>5.0.0-preview.1.20113.3</MicrosoftEntityFrameworkCoreRelationalPackageVersion>
<MicrosoftEntityFrameworkCoreSqlitePackageVersion>5.0.0-preview.1.20113.3</MicrosoftEntityFrameworkCoreSqlitePackageVersion>
<MicrosoftEntityFrameworkCoreSqlServerPackageVersion>5.0.0-preview.1.20113.3</MicrosoftEntityFrameworkCoreSqlServerPackageVersion>
<MicrosoftEntityFrameworkCoreToolsPackageVersion>5.0.0-preview.1.20113.3</MicrosoftEntityFrameworkCoreToolsPackageVersion>
<MicrosoftEntityFrameworkCorePackageVersion>5.0.0-preview.1.20113.3</MicrosoftEntityFrameworkCorePackageVersion>
<dotnetefPackageVersion>5.0.0-preview.2.20120.6</dotnetefPackageVersion>
<MicrosoftEntityFrameworkCoreInMemoryPackageVersion>5.0.0-preview.2.20120.6</MicrosoftEntityFrameworkCoreInMemoryPackageVersion>
<MicrosoftEntityFrameworkCoreRelationalPackageVersion>5.0.0-preview.2.20120.6</MicrosoftEntityFrameworkCoreRelationalPackageVersion>
<MicrosoftEntityFrameworkCoreSqlitePackageVersion>5.0.0-preview.2.20120.6</MicrosoftEntityFrameworkCoreSqlitePackageVersion>
<MicrosoftEntityFrameworkCoreSqlServerPackageVersion>5.0.0-preview.2.20120.6</MicrosoftEntityFrameworkCoreSqlServerPackageVersion>
<MicrosoftEntityFrameworkCoreToolsPackageVersion>5.0.0-preview.2.20120.6</MicrosoftEntityFrameworkCoreToolsPackageVersion>
<MicrosoftEntityFrameworkCorePackageVersion>5.0.0-preview.2.20120.6</MicrosoftEntityFrameworkCorePackageVersion>
<!-- Packages from dotnet/aspnetcore-tooling -->
<MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>5.0.0-preview.1.20114.5</MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion>
<MicrosoftAspNetCoreRazorLanguagePackageVersion>5.0.0-preview.1.20114.5</MicrosoftAspNetCoreRazorLanguagePackageVersion>

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Core components of ASP.NET Core Kestrel cross-platform web server.</Description>
@ -16,7 +16,9 @@
<Compile Include="$(SharedSourceRoot)CertificateGeneration\**\*.cs" />
<Compile Include="$(SharedSourceRoot)ValueTaskExtensions\**\*.cs" />
<Compile Include="$(SharedSourceRoot)UrlDecoder\**\*.cs" />
<Compile Include="$(SharedSourceRoot)runtime\**\*.cs" LinkBase="Shared\runtime\" />
<Compile Include="$(SharedSourceRoot)runtime\*.cs" Link="Shared\runtime\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)runtime\Http2\**\*.cs" Link="Shared\runtime\Http2\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)runtime\Http3\**\*.cs" Link="Shared\runtime\Http3\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\**\*.cs" LinkBase="Shared\" />
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
</ItemGroup>

View File

@ -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";
}
}

View File

@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>Libuv transport for the ASP.NET Core Kestrel cross-platform web server.</Description>
<Description>Quic transport for the ASP.NET Core Kestrel cross-platform web server.</Description>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageTags>aspnetcore;kestrel</PackageTags>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>CS1591;$(NoWarn)</NoWarn>
<NoWarn>CS1591;CS0436;$(NoWarn)</NoWarn><!-- Conflicts between internal and public Quic APIs -->
<IsShippingPackage>false</IsShippingPackage>
</PropertyGroup>
@ -14,9 +14,13 @@
<Compile Include="$(RepoRoot)src\Shared\Buffers.MemoryPool\*.cs" LinkBase="MemoryPool" />
<Compile Include="$(KestrelSharedSourceRoot)\CorrelationIdGenerator.cs" Link="Internal\CorrelationIdGenerator.cs" />
<Compile Include="$(SharedSourceRoot)ServerInfrastructure\DuplexPipe.cs" Link="Internal\DuplexPipe.cs" />
<Compile Include="$(SharedSourceRoot)runtime\NetEventSource.Common.cs" Link="Shared\runtime\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)runtime\SR.Quic.cs" Link="Shared\runtime\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)runtime\Quic\**\*.cs" Link="Shared\runtime\Quic\%(Filename)%(Extension)" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.cs" Link="Internal\TransportConnection.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.Generated.cs" Link="Internal\TransportConnection.Generated.cs" />
<Compile Include="$(KestrelSharedSourceRoot)\TransportConnection.FeatureCollection.cs" Link="Internal\TransportConnection.FeatureCollection.cs" />
<Compile Include="$(RepoRoot)src\Shared\TaskToApm.cs" Link="Internal\TaskToApm.cs" />
</ItemGroup>
<ItemGroup>
@ -26,4 +30,11 @@
<Reference Include="Microsoft.Extensions.Options" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="$(SharedSourceRoot)runtime\SR.resx" Link="Shared\runtime\SR.resx">
<ManifestResourceName>System.Net.Quic.SR</ManifestResourceName>
<Generator></Generator>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@ -12,7 +12,7 @@ namespace System.Net.Http.HPack
{
internal class HPackDecoder
{
private enum State
private enum State : byte
{
Ready,
HeaderFieldIndex,

View File

@ -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; }

View File

@ -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;

View File

@ -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.
/// <summary>Provides logging facilities for System.Net libraries.</summary>
#if NET46
[SecuritySafeCritical]
#endif
internal sealed partial class NetEventSource : EventSource
{
/// <summary>The single event source instance to use for all logging.</summary>
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
/// <summary>Logs entrance to a method.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="formattableString">A description of the entrance, including any arguments to the call.</param>
/// <param name="memberName">The calling member.</param>
[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);
}
/// <summary>Logs entrance to a method.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="arg0">The object to log.</param>
/// <param name="memberName">The calling member.</param>
[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)})");
}
/// <summary>Logs entrance to a method.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="arg0">The first object to log.</param>
/// <param name="arg1">The second object to log.</param>
/// <param name="memberName">The calling member.</param>
[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)})");
}
/// <summary>Logs entrance to a method.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="arg0">The first object to log.</param>
/// <param name="arg1">The second object to log.</param>
/// <param name="arg2">The third object to log.</param>
/// <param name="memberName">The calling member.</param>
[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
/// <summary>Logs exit from a method.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="formattableString">A description of the exit operation, including any return values.</param>
/// <param name="memberName">The calling member.</param>
[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);
}
/// <summary>Logs exit from a method.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="arg0">A return value from the member.</param>
/// <param name="memberName">The calling member.</param>
[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());
}
/// <summary>Logs exit from a method.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="arg0">A return value from the member.</param>
/// <param name="arg1">A second return value from the member.</param>
/// <param name="memberName">The calling member.</param>
[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
/// <summary>Logs an information message.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="formattableString">The message to be logged.</param>
/// <param name="memberName">The calling member.</param>
[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);
}
/// <summary>Logs an information message.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="message">The message to be logged.</param>
/// <param name="memberName">The calling member.</param>
[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
/// <summary>Logs an error message.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="formattableString">The message to be logged.</param>
/// <param name="memberName">The calling member.</param>
[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));
}
/// <summary>Logs an error message.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="message">The message to be logged.</param>
/// <param name="memberName">The calling member.</param>
[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
/// <summary>Logs a fatal error and raises an assert.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="formattableString">The message to be logged.</param>
/// <param name="memberName">The calling member.</param>
[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}");
}
/// <summary>Logs a fatal error and raises an assert.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="message">The message to be logged.</param>
/// <param name="memberName">The calling member.</param>
[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
/// <summary>Logs the contents of a buffer.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="buffer">The buffer to be logged.</param>
/// <param name="memberName">The calling member.</param>
[NonEvent]
public static void DumpBuffer(object? thisOrContextObject, byte[] buffer, [CallerMemberName] string? memberName = null)
{
DumpBuffer(thisOrContextObject, buffer, 0, buffer.Length, memberName);
}
/// <summary>Logs the contents of a buffer.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="buffer">The buffer to be logged.</param>
/// <param name="offset">The starting offset from which to log.</param>
/// <param name="count">The number of bytes to log.</param>
/// <param name="memberName">The calling member.</param>
[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);
}
}
/// <summary>Logs the contents of a buffer.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="bufferPtr">The starting location of the buffer to be logged.</param>
/// <param name="count">The number of bytes to log.</param>
/// <param name="memberName">The calling member.</param>
[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
/// <summary>Logs a relationship between two objects.</summary>
/// <param name="first">The first object.</param>
/// <param name="second">The second object.</param>
/// <param name="memberName">The calling member.</param>
[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));
}
/// <summary>Logs a relationship between two objects.</summary>
/// <param name="thisOrContextObject">`this`, or another object that serves to provide context for the operation.</param>
/// <param name="first">The first object.</param>
/// <param name="second">The second object.</param>
/// <param name="memberName">The calling member.</param>
[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>(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<byte>();
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
}
}

View File

@ -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<Socket> 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<QuicStreamProvider> 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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<QuicConnectionProvider> 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);
}
}
}

View File

@ -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<byte> buffer)
{
CheckDisposed();
if (!_canRead)
{
throw new NotSupportedException();
}
return _socket.Receive(buffer);
}
internal override async ValueTask<int> ReadAsync(Memory<byte> 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<byte> buffer)
{
CheckDisposed();
if (!_canWrite)
{
throw new NotSupportedException();
}
_socket.Send(buffer);
}
internal override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
return WriteAsync(buffer, endStream: false, cancellationToken);
}
internal override async ValueTask WriteAsync(ReadOnlyMemory<byte> 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<byte> buffers, CancellationToken cancellationToken = default)
{
return WriteAsync(buffers, endStream: false, cancellationToken);
}
internal override async ValueTask WriteAsync(ReadOnlySequence<byte> buffers, bool endStream, CancellationToken cancellationToken = default)
{
CheckDisposed();
if (!_canWrite)
{
throw new NotSupportedException();
}
if (_socket == null)
{
await ConnectAsync(cancellationToken).ConfigureAwait(false);
}
foreach (ReadOnlyMemory<byte> buffer in buffers)
{
await _socket.SendAsync(buffer, SocketFlags.None, cancellationToken).ConfigureAwait(false);
}
if (endStream)
{
_socket.Shutdown(SocketShutdown.Send);
}
}
internal override ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, CancellationToken cancellationToken = default)
{
return WriteAsync(buffers, endStream: false, cancellationToken);
}
internal override async ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, bool endStream, CancellationToken cancellationToken = default)
{
CheckDisposed();
if (!_canWrite)
{
throw new NotSupportedException();
}
if (_socket == null)
{
await ConnectAsync(cancellationToken).ConfigureAwait(false);
}
foreach (ReadOnlyMemory<byte> 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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<MsQuicNativeMethods.RegistrationOpenDelegate>(
nativeRegistration.RegistrationOpen);
RegistrationCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.RegistrationCloseDelegate>(
nativeRegistration.RegistrationClose);
SecConfigCreateDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SecConfigCreateDelegate>(
nativeRegistration.SecConfigCreate);
SecConfigDeleteDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SecConfigDeleteDelegate>(
nativeRegistration.SecConfigDelete);
SessionOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionOpenDelegate>(
nativeRegistration.SessionOpen);
SessionCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionCloseDelegate>(
nativeRegistration.SessionClose);
SessionShutdownDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SessionShutdownDelegate>(
nativeRegistration.SessionShutdown);
ListenerOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerOpenDelegate>(
nativeRegistration.ListenerOpen);
ListenerCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerCloseDelegate>(
nativeRegistration.ListenerClose);
ListenerStartDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerStartDelegate>(
nativeRegistration.ListenerStart);
ListenerStopDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ListenerStopDelegate>(
nativeRegistration.ListenerStop);
ConnectionOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionOpenDelegate>(
nativeRegistration.ConnectionOpen);
ConnectionCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionCloseDelegate>(
nativeRegistration.ConnectionClose);
ConnectionShutdownDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionShutdownDelegate>(
nativeRegistration.ConnectionShutdown);
ConnectionStartDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.ConnectionStartDelegate>(
nativeRegistration.ConnectionStart);
StreamOpenDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamOpenDelegate>(
nativeRegistration.StreamOpen);
StreamCloseDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamCloseDelegate>(
nativeRegistration.StreamClose);
StreamStartDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamStartDelegate>(
nativeRegistration.StreamStart);
StreamShutdownDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamShutdownDelegate>(
nativeRegistration.StreamShutdown);
StreamSendDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamSendDelegate>(
nativeRegistration.StreamSend);
StreamReceiveCompleteDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamReceiveCompleteDelegate>(
nativeRegistration.StreamReceiveComplete);
StreamReceiveSetEnabledDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.StreamReceiveSetEnabledDelegate>(
nativeRegistration.StreamReceiveSetEnabled);
SetContextDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetContextDelegate>(
nativeRegistration.SetContext);
GetContextDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.GetContextDelegate>(
nativeRegistration.GetContext);
SetCallbackHandlerDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetCallbackHandlerDelegate>(
nativeRegistration.SetCallbackHandler);
SetParamDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.SetParamDelegate>(
nativeRegistration.SetParam);
GetParamDelegate =
Marshal.GetDelegateForFunctionPointer<MsQuicNativeMethods.GetParamDelegate>(
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<MsQuicSecurityConfig> CreateSecurityConfig(X509Certificate certificate, string certFilePath, string privateKeyFilePath)
{
MsQuicSecurityConfig secConfig = null;
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
uint secConfigCreateStatus = MsQuicStatusCodes.InternalError;
uint createConfigStatus;
IntPtr unmanagedAddr = IntPtr.Zero;
MsQuicNativeMethods.CertFileParams fileParams = default;
try
{
if (certFilePath != null && privateKeyFilePath != null)
{
fileParams = new MsQuicNativeMethods.CertFileParams
{
CertificateFilePath = Marshal.StringToCoTaskMemUTF8(certFilePath),
PrivateKeyFilePath = Marshal.StringToCoTaskMemUTF8(privateKeyFilePath)
};
unmanagedAddr = Marshal.AllocHGlobal(Marshal.SizeOf(fileParams));
Marshal.StructureToPtr(fileParams, unmanagedAddr, fDeleteOld: false);
createConfigStatus = SecConfigCreateDelegate(
_registrationContext,
(uint)QUIC_SEC_CONFIG_FLAG.CERT_FILE,
certificate.Handle,
null,
IntPtr.Zero,
SecCfgCreateCallbackHandler);
}
else if (certificate != null)
{
createConfigStatus = SecConfigCreateDelegate(
_registrationContext,
(uint)QUIC_SEC_CONFIG_FLAG.CERT_CONTEXT,
certificate.Handle,
null,
IntPtr.Zero,
SecCfgCreateCallbackHandler);
}
else
{
// If no certificate is provided, provide a null one.
createConfigStatus = SecConfigCreateDelegate(
_registrationContext,
(uint)QUIC_SEC_CONFIG_FLAG.CERT_NULL,
IntPtr.Zero,
null,
IntPtr.Zero,
SecCfgCreateCallbackHandler);
}
QuicExceptionHelpers.ThrowIfFailed(
createConfigStatus,
"Could not create security configuration.");
void SecCfgCreateCallbackHandler(
IntPtr context,
uint status,
IntPtr securityConfig)
{
secConfig = new MsQuicSecurityConfig(this, securityConfig);
secConfigCreateStatus = status;
tcs.SetResult(null);
}
await tcs.Task.ConfigureAwait(false);
QuicExceptionHelpers.ThrowIfFailed(
secConfigCreateStatus,
"Could not create security configuration.");
}
finally
{
if (fileParams.CertificateFilePath != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(fileParams.CertificateFilePath);
}
if (fileParams.PrivateKeyFilePath != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(fileParams.PrivateKeyFilePath);
}
if (unmanagedAddr != IntPtr.Zero)
{
Marshal.FreeHGlobal(unmanagedAddr);
}
}
return secConfig;
}
public IntPtr SessionOpen(byte[] alpn)
{
IntPtr sessionPtr = IntPtr.Zero;
uint status = SessionOpenDelegate(
_registrationContext,
alpn,
IntPtr.Zero,
ref sessionPtr);
QuicExceptionHelpers.ThrowIfFailed(status, "Could not open session.");
return sessionPtr;
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
~MsQuicApi()
{
Dispose(disposing: false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
RegistrationCloseDelegate?.Invoke(_registrationContext);
_disposed = true;
}
}
}

View File

@ -0,0 +1,98 @@
// 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 static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
internal static class MsQuicParameterHelpers
{
internal static unsafe SOCKADDR_INET GetINetParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param)
{
byte* ptr = stackalloc byte[sizeof(SOCKADDR_INET)];
QuicBuffer buffer = new QuicBuffer
{
Length = (uint)sizeof(SOCKADDR_INET),
Buffer = ptr
};
QuicExceptionHelpers.ThrowIfFailed(
api.UnsafeGetParam(nativeObject, level, param, ref buffer),
"Could not get SOCKADDR_INET.");
return *(SOCKADDR_INET*)ptr;
}
internal static unsafe ushort GetUShortParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param)
{
byte* ptr = stackalloc byte[sizeof(ushort)];
QuicBuffer buffer = new QuicBuffer()
{
Length = sizeof(ushort),
Buffer = ptr
};
QuicExceptionHelpers.ThrowIfFailed(
api.UnsafeGetParam(nativeObject, level, param, ref buffer),
"Could not get ushort.");
return *(ushort*)ptr;
}
internal static unsafe void SetUshortParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ushort value)
{
QuicBuffer buffer = new QuicBuffer()
{
Length = sizeof(ushort),
Buffer = (byte*)&value
};
QuicExceptionHelpers.ThrowIfFailed(
api.UnsafeSetParam(nativeObject, level, param, buffer),
"Could not set ushort.");
}
internal static unsafe ulong GetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param)
{
byte* ptr = stackalloc byte[sizeof(ulong)];
QuicBuffer buffer = new QuicBuffer()
{
Length = sizeof(ulong),
Buffer = ptr
};
QuicExceptionHelpers.ThrowIfFailed(
api.UnsafeGetParam(nativeObject, level, param, ref buffer),
"Could not get ulong.");
return *(ulong*)ptr;
}
internal static unsafe void SetULongParam(MsQuicApi api, IntPtr nativeObject, uint level, uint param, ulong value)
{
QuicBuffer buffer = new QuicBuffer()
{
Length = sizeof(ulong),
Buffer = (byte*)&value
};
QuicExceptionHelpers.ThrowIfFailed(
api.UnsafeGetParam(nativeObject, level, param, ref buffer),
"Could not set ulong.");
}
internal static unsafe void SetSecurityConfig(MsQuicApi api, IntPtr nativeObject, uint level, uint param, IntPtr value)
{
QuicBuffer buffer = new QuicBuffer()
{
Length = (uint)sizeof(void*),
Buffer = (byte*)&value
};
QuicExceptionHelpers.ThrowIfFailed(
api.UnsafeSetParam(nativeObject, level, param, buffer),
"Could not set security configuration.");
}
}
}

View File

@ -0,0 +1,45 @@
// 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.
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
// TODO this will eventually be abstracted to support both Client and Server
// certificates
internal class MsQuicSecurityConfig : IDisposable
{
private bool _disposed;
private MsQuicApi _registration;
public MsQuicSecurityConfig(MsQuicApi registration, IntPtr nativeObjPtr)
{
_registration = registration;
NativeObjPtr = nativeObjPtr;
}
public IntPtr NativeObjPtr { get; private set; }
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
_registration.SecConfigDeleteDelegate?.Invoke(NativeObjPtr);
NativeObjPtr = IntPtr.Zero;
_disposed = true;
}
~MsQuicSecurityConfig()
{
Dispose(disposing: false);
}
}
}

View File

@ -0,0 +1,156 @@
// 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.
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
internal sealed class MsQuicSession : IDisposable
{
private bool _disposed = false;
private IntPtr _nativeObjPtr;
private bool _opened;
internal MsQuicSession()
{
if (!MsQuicApi.IsQuicSupported)
{
throw new NotSupportedException(SR.net_quic_notsupported);
}
}
public IntPtr ConnectionOpen(QuicClientConnectionOptions options)
{
if (!_opened)
{
OpenSession(options.ClientAuthenticationOptions.ApplicationProtocols[0].Protocol.ToArray(),
(ushort)options.MaxBidirectionalStreams,
(ushort)options.MaxUnidirectionalStreams);
}
QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ConnectionOpenDelegate(
_nativeObjPtr,
MsQuicConnection.NativeCallbackHandler,
IntPtr.Zero,
out IntPtr connectionPtr),
"Could not open the connection.");
return connectionPtr;
}
private void OpenSession(byte[] alpn, ushort bidirectionalStreamCount, ushort undirectionalStreamCount)
{
_opened = true;
_nativeObjPtr = MsQuicApi.Api.SessionOpen(alpn);
SetPeerBiDirectionalStreamCount(bidirectionalStreamCount);
SetPeerUnidirectionalStreamCount(undirectionalStreamCount);
}
// TODO allow for a callback to select the certificate (SNI).
public IntPtr ListenerOpen(QuicListenerOptions options)
{
if (!_opened)
{
OpenSession(options.ServerAuthenticationOptions.ApplicationProtocols[0].Protocol.ToArray(),
(ushort)options.MaxBidirectionalStreams,
(ushort)options.MaxUnidirectionalStreams);
}
QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ListenerOpenDelegate(
_nativeObjPtr,
MsQuicListener.NativeCallbackHandler,
IntPtr.Zero,
out IntPtr listenerPointer),
"Could not open listener.");
return listenerPointer;
}
// TODO call this for graceful shutdown?
public void ShutDown(
QUIC_CONNECTION_SHUTDOWN_FLAG Flags,
ushort ErrorCode)
{
MsQuicApi.Api.SessionShutdownDelegate(
_nativeObjPtr,
(uint)Flags,
ErrorCode);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void SetPeerBiDirectionalStreamCount(ushort count)
{
SetUshortParamter(QUIC_PARAM_SESSION.PEER_BIDI_STREAM_COUNT, count);
}
public void SetPeerUnidirectionalStreamCount(ushort count)
{
SetUshortParamter(QUIC_PARAM_SESSION.PEER_UNIDI_STREAM_COUNT, count);
}
private unsafe void SetUshortParamter(QUIC_PARAM_SESSION param, ushort count)
{
var buffer = new MsQuicNativeMethods.QuicBuffer()
{
Length = sizeof(ushort),
Buffer = (byte*)&count
};
SetParam(param, buffer);
}
public void SetDisconnectTimeout(TimeSpan timeout)
{
SetULongParamter(QUIC_PARAM_SESSION.DISCONNECT_TIMEOUT, (ulong)timeout.TotalMilliseconds);
}
public void SetIdleTimeout(TimeSpan timeout)
{
SetULongParamter(QUIC_PARAM_SESSION.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds);
}
private unsafe void SetULongParamter(QUIC_PARAM_SESSION param, ulong count)
{
var buffer = new MsQuicNativeMethods.QuicBuffer()
{
Length = sizeof(ulong),
Buffer = (byte*)&count
};
SetParam(param, buffer);
}
private void SetParam(
QUIC_PARAM_SESSION param,
MsQuicNativeMethods.QuicBuffer buf)
{
QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.UnsafeSetParam(
_nativeObjPtr,
(uint)QUIC_PARAM_LEVEL.SESSION,
(uint)param,
buf),
"Could not set parameter on session.");
}
~MsQuicSession()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
MsQuicApi.Api.SessionCloseDelegate?.Invoke(_nativeObjPtr);
_nativeObjPtr = IntPtr.Zero;
_disposed = true;
}
}
}

View File

@ -0,0 +1,17 @@
// 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.
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
internal static class QuicExceptionHelpers
{
internal static void ThrowIfFailed(uint status, string message = null, Exception innerException = null)
{
if (!MsQuicStatusHelper.SuccessfulStatusCode(status))
{
throw new QuicException($"{message} Error Code: {MsQuicStatusCodes.GetError(status)}");
}
}
}
}

View File

@ -0,0 +1,81 @@
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
/// <summary>
/// A resettable completion source which can be completed multiple times.
/// Used to make methods async between completed events and their associated async method.
/// </summary>
internal class ResettableCompletionSource<T> : IValueTaskSource<T>, IValueTaskSource
{
protected ManualResetValueTaskSourceCore<T> _valueTaskSource;
public ResettableCompletionSource()
{
_valueTaskSource.RunContinuationsAsynchronously = true;
}
public ValueTask<T> GetValueTask()
{
return new ValueTask<T>(this, _valueTaskSource.Version);
}
public ValueTask GetTypelessValueTask()
{
return new ValueTask(this, _valueTaskSource.Version);
}
public ValueTaskSourceStatus GetStatus(short token)
{
return _valueTaskSource.GetStatus(token);
}
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{
_valueTaskSource.OnCompleted(continuation, state, token, flags);
}
public void Complete(T result)
{
_valueTaskSource.SetResult(result);
}
public void CompleteException(Exception ex)
{
_valueTaskSource.SetException(ex);
}
public T GetResult(short token)
{
bool isValid = token == _valueTaskSource.Version;
try
{
return _valueTaskSource.GetResult(token);
}
finally
{
if (isValid)
{
_valueTaskSource.Reset();
}
}
}
void IValueTaskSource.GetResult(short token)
{
bool isValid = token == _valueTaskSource.Version;
try
{
_valueTaskSource.GetResult(token);
}
finally
{
if (isValid)
{
_valueTaskSource.Reset();
}
}
}
}
}

View File

@ -0,0 +1,416 @@
// 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.Quic.Implementations.MsQuic.Internal;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
namespace System.Net.Quic.Implementations.MsQuic
{
internal sealed class MsQuicConnection : QuicConnectionProvider
{
private MsQuicSession _session;
// Pointer to the underlying connection
// TODO replace all IntPtr with SafeHandles
private IntPtr _ptr;
// Handle to this object for native callbacks.
private GCHandle _handle;
// Delegate that wraps the static function that will be called when receiving an event.
// TODO investigate if the delegate can be static instead.
private ConnectionCallbackDelegate _connectionDelegate;
// Endpoint to either connect to or the endpoint already accepted.
private IPEndPoint _localEndPoint;
private readonly IPEndPoint _remoteEndPoint;
private readonly ResettableCompletionSource<uint> _connectTcs = new ResettableCompletionSource<uint>();
private readonly ResettableCompletionSource<uint> _shutdownTcs = new ResettableCompletionSource<uint>();
private bool _disposed;
private bool _connected;
private MsQuicSecurityConfig _securityConfig;
private long _abortErrorCode = -1;
// Queue for accepted streams
private readonly Channel<MsQuicStream> _acceptQueue = Channel.CreateUnbounded<MsQuicStream>(new UnboundedChannelOptions()
{
SingleReader = true,
SingleWriter = true,
});
// constructor for inbound connections
public MsQuicConnection(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, IntPtr nativeObjPtr)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
_localEndPoint = localEndPoint;
_remoteEndPoint = remoteEndPoint;
_ptr = nativeObjPtr;
SetCallbackHandler();
SetIdleTimeout(TimeSpan.FromSeconds(120));
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
}
// constructor for outbound connections
public MsQuicConnection(QuicClientConnectionOptions options)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
// TODO need to figure out if/how we want to expose sessions
// Creating a session per connection isn't ideal.
_session = new MsQuicSession();
_ptr = _session.ConnectionOpen(options);
_remoteEndPoint = options.RemoteEndPoint;
SetCallbackHandler();
SetIdleTimeout(options.IdleTimeout);
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
}
internal override IPEndPoint LocalEndPoint
{
get
{
return new IPEndPoint(_localEndPoint.Address, _localEndPoint.Port);
}
}
internal async ValueTask SetSecurityConfigForConnection(X509Certificate cert, string certFilePath, string privateKeyFilePath)
{
_securityConfig = await MsQuicApi.Api.CreateSecurityConfig(cert, certFilePath, privateKeyFilePath);
// TODO this isn't being set correctly
MsQuicParameterHelpers.SetSecurityConfig(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.SEC_CONFIG, _securityConfig.NativeObjPtr);
}
internal override IPEndPoint RemoteEndPoint => new IPEndPoint(_remoteEndPoint.Address, _remoteEndPoint.Port);
internal override SslApplicationProtocol NegotiatedApplicationProtocol => throw new NotImplementedException();
internal override bool Connected => _connected;
internal uint HandleEvent(ref ConnectionEvent connectionEvent)
{
uint status = MsQuicStatusCodes.Success;
try
{
switch (connectionEvent.Type)
{
// Connection is connected, can start to create streams.
case QUIC_CONNECTION_EVENT.CONNECTED:
{
status = HandleEventConnected(
connectionEvent);
}
break;
// Connection is being closed by the transport
case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_TRANSPORT:
{
status = HandleEventShutdownInitiatedByTransport(
connectionEvent);
}
break;
// Connection is being closed by the peer
case QUIC_CONNECTION_EVENT.SHUTDOWN_INITIATED_BY_PEER:
{
status = HandleEventShutdownInitiatedByPeer(
connectionEvent);
}
break;
// Connection has been shutdown
case QUIC_CONNECTION_EVENT.SHUTDOWN_COMPLETE:
{
status = HandleEventShutdownComplete(
connectionEvent);
}
break;
case QUIC_CONNECTION_EVENT.PEER_STREAM_STARTED:
{
status = HandleEventNewStream(
connectionEvent);
}
break;
case QUIC_CONNECTION_EVENT.STREAMS_AVAILABLE:
{
status = HandleEventStreamsAvailable(
connectionEvent);
}
break;
default:
break;
}
}
catch (Exception)
{
// TODO we may want to either add a debug assert here or return specific error codes
// based on the exception caught.
return MsQuicStatusCodes.InternalError;
}
return status;
}
private uint HandleEventConnected(ConnectionEvent connectionEvent)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS);
_localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress);
_connected = true;
// I don't believe we need to lock here because
// handle event connected will not be called at the same time as
// handle event shutdown initiated by transport
_connectTcs.Complete(MsQuicStatusCodes.Success);
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return MsQuicStatusCodes.Success;
}
private uint HandleEventShutdownInitiatedByTransport(ConnectionEvent connectionEvent)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
if (!_connected)
{
_connectTcs.CompleteException(new IOException("Connection has been shutdown."));
}
_acceptQueue.Writer.Complete();
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return MsQuicStatusCodes.Success;
}
private uint HandleEventShutdownInitiatedByPeer(ConnectionEvent connectionEvent)
{
_abortErrorCode = connectionEvent.Data.ShutdownBeginPeer.ErrorCode;
_acceptQueue.Writer.Complete();
return MsQuicStatusCodes.Success;
}
private uint HandleEventShutdownComplete(ConnectionEvent connectionEvent)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
_shutdownTcs.Complete(MsQuicStatusCodes.Success);
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return MsQuicStatusCodes.Success;
}
private uint HandleEventNewStream(ConnectionEvent connectionEvent)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
MsQuicStream msQuicStream = new MsQuicStream(this, connectionEvent.StreamFlags, connectionEvent.Data.NewStream.Stream, inbound: true);
_acceptQueue.Writer.TryWrite(msQuicStream);
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return MsQuicStatusCodes.Success;
}
private uint HandleEventStreamsAvailable(ConnectionEvent connectionEvent)
{
return MsQuicStatusCodes.Success;
}
internal override async ValueTask<QuicStreamProvider> AcceptStreamAsync(CancellationToken cancellationToken = default)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
ThrowIfDisposed();
MsQuicStream stream;
try
{
stream = await _acceptQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
}
catch (ChannelClosedException)
{
throw _abortErrorCode switch
{
-1 => new QuicOperationAbortedException(), // Shutdown initiated by us.
long err => new QuicConnectionAbortedException(err) // Shutdown initiated by peer.
};
}
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return stream;
}
internal override QuicStreamProvider OpenUnidirectionalStream()
{
ThrowIfDisposed();
return StreamOpen(QUIC_STREAM_OPEN_FLAG.UNIDIRECTIONAL);
}
internal override QuicStreamProvider OpenBidirectionalStream()
{
ThrowIfDisposed();
return StreamOpen(QUIC_STREAM_OPEN_FLAG.NONE);
}
internal override long GetRemoteAvailableUnidirectionalStreamCount()
{
return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.PEER_UNIDI_STREAM_COUNT);
}
internal override long GetRemoteAvailableBidirectionalStreamCount()
{
return MsQuicParameterHelpers.GetUShortParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.PEER_BIDI_STREAM_COUNT);
}
private unsafe void SetIdleTimeout(TimeSpan timeout)
{
MsQuicParameterHelpers.SetULongParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.IDLE_TIMEOUT, (ulong)timeout.TotalMilliseconds);
}
internal override ValueTask ConnectAsync(CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
QuicExceptionHelpers.ThrowIfFailed(
MsQuicApi.Api.ConnectionStartDelegate(
_ptr,
(ushort)_remoteEndPoint.AddressFamily,
_remoteEndPoint.Address.ToString(),
(ushort)_remoteEndPoint.Port),
"Failed to connect to peer.");
return _connectTcs.GetTypelessValueTask();
}
private MsQuicStream StreamOpen(
QUIC_STREAM_OPEN_FLAG flags)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
IntPtr streamPtr = IntPtr.Zero;
QuicExceptionHelpers.ThrowIfFailed(
MsQuicApi.Api.StreamOpenDelegate(
_ptr,
(uint)flags,
MsQuicStream.NativeCallbackHandler,
IntPtr.Zero,
out streamPtr),
"Failed to open stream to peer.");
MsQuicStream stream = new MsQuicStream(this, flags, streamPtr, inbound: false);
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return stream;
}
private void SetCallbackHandler()
{
_handle = GCHandle.Alloc(this);
_connectionDelegate = new ConnectionCallbackDelegate(NativeCallbackHandler);
MsQuicApi.Api.SetCallbackHandlerDelegate(
_ptr,
_connectionDelegate,
GCHandle.ToIntPtr(_handle));
}
private ValueTask ShutdownAsync(
QUIC_CONNECTION_SHUTDOWN_FLAG Flags,
long ErrorCode)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
uint status = MsQuicApi.Api.ConnectionShutdownDelegate(
_ptr,
(uint)Flags,
ErrorCode);
QuicExceptionHelpers.ThrowIfFailed(status, "Failed to shutdown connection.");
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return _shutdownTcs.GetTypelessValueTask();
}
internal static uint NativeCallbackHandler(
IntPtr connection,
IntPtr context,
ref ConnectionEvent connectionEventStruct)
{
GCHandle handle = GCHandle.FromIntPtr(context);
MsQuicConnection quicConnection = (MsQuicConnection)handle.Target;
return quicConnection.HandleEvent(ref connectionEventStruct);
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~MsQuicConnection()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
if (_ptr != IntPtr.Zero)
{
MsQuicApi.Api.ConnectionCloseDelegate?.Invoke(_ptr);
}
_ptr = IntPtr.Zero;
if (disposing)
{
_handle.Free();
_session?.Dispose();
_securityConfig?.Dispose();
}
_disposed = true;
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
}
internal override ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default)
{
ThrowIfDisposed();
return ShutdownAsync(QUIC_CONNECTION_SHUTDOWN_FLAG.NONE, errorCode);
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MsQuicStream));
}
}
}
}

View File

@ -0,0 +1,22 @@
// 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.Quic.Implementations.MsQuic.Internal;
using System.Net.Security;
namespace System.Net.Quic.Implementations.MsQuic
{
internal sealed class MsQuicImplementationProvider : QuicImplementationProvider
{
internal override QuicListenerProvider CreateListener(QuicListenerOptions options)
{
return new MsQuicListener(options);
}
internal override QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options)
{
return new MsQuicConnection(options);
}
}
}

View File

@ -0,0 +1,213 @@
// 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.Quic.Implementations.MsQuic.Internal;
using System.Net.Security;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
namespace System.Net.Quic.Implementations.MsQuic
{
internal sealed class MsQuicListener : QuicListenerProvider, IDisposable
{
// Security configuration for MsQuic
private MsQuicSession _session;
// Pointer to the underlying listener
// TODO replace all IntPtr with SafeHandles
private IntPtr _ptr;
// Handle to this object for native callbacks.
private GCHandle _handle;
// Delegate that wraps the static function that will be called when receiving an event.
private ListenerCallbackDelegate _listenerDelegate;
// Ssl listening options (ALPN, cert, etc)
private SslServerAuthenticationOptions _sslOptions;
private QuicListenerOptions _options;
private volatile bool _disposed;
private IPEndPoint _listenEndPoint;
private readonly Channel<MsQuicConnection> _acceptConnectionQueue;
internal MsQuicListener(QuicListenerOptions options)
{
_session = new MsQuicSession();
_acceptConnectionQueue = Channel.CreateBounded<MsQuicConnection>(new BoundedChannelOptions(options.ListenBacklog)
{
SingleReader = true,
SingleWriter = true
});
_options = options;
_sslOptions = options.ServerAuthenticationOptions;
_listenEndPoint = options.ListenEndPoint;
_ptr = _session.ListenerOpen(options);
}
internal override IPEndPoint ListenEndPoint
{
get
{
return new IPEndPoint(_listenEndPoint.Address, _listenEndPoint.Port);
}
}
internal override async ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default)
{
if (NetEventSource.IsEnabled) NetEventSource.Enter(this);
ThrowIfDisposed();
MsQuicConnection connection;
try
{
connection = await _acceptConnectionQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(false);
}
catch (ChannelClosedException)
{
throw new QuicOperationAbortedException();
}
await connection.SetSecurityConfigForConnection(_sslOptions.ServerCertificate,
_options.CertificateFilePath,
_options.PrivateKeyFilePath);
if (NetEventSource.IsEnabled) NetEventSource.Exit(this);
return connection;
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~MsQuicListener()
{
Dispose(false);
}
private void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
StopAcceptingConnections();
if (_ptr != IntPtr.Zero)
{
MsQuicApi.Api.ListenerStopDelegate(_ptr);
MsQuicApi.Api.ListenerCloseDelegate(_ptr);
}
_ptr = IntPtr.Zero;
// TODO this call to session dispose hangs.
//_session.Dispose();
_disposed = true;
}
internal override void Start()
{
ThrowIfDisposed();
SetCallbackHandler();
SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet(_listenEndPoint);
QuicExceptionHelpers.ThrowIfFailed(MsQuicApi.Api.ListenerStartDelegate(
_ptr,
ref address),
"Failed to start listener.");
SetListenPort();
}
internal override void Close()
{
ThrowIfDisposed();
MsQuicApi.Api.ListenerStopDelegate(_ptr);
}
private unsafe void SetListenPort()
{
SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _ptr, (uint)QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS);
_listenEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(inetAddress);
}
internal unsafe uint ListenerCallbackHandler(
ref ListenerEvent evt)
{
try
{
switch (evt.Type)
{
case QUIC_LISTENER_EVENT.NEW_CONNECTION:
{
NewConnectionInfo connectionInfo = *(NewConnectionInfo*)evt.Data.NewConnection.Info;
IPEndPoint localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(*(SOCKADDR_INET*)connectionInfo.LocalAddress);
IPEndPoint remoteEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(*(SOCKADDR_INET*)connectionInfo.RemoteAddress);
MsQuicConnection msQuicConnection = new MsQuicConnection(localEndPoint, remoteEndPoint, evt.Data.NewConnection.Connection);
_acceptConnectionQueue.Writer.TryWrite(msQuicConnection);
}
// Always pend the new connection to wait for the security config to be resolved
// TODO this doesn't need to be async always
return MsQuicStatusCodes.Pending;
default:
return MsQuicStatusCodes.InternalError;
}
}
catch (Exception)
{
return MsQuicStatusCodes.InternalError;
}
}
private void StopAcceptingConnections()
{
_acceptConnectionQueue.Writer.TryComplete();
}
internal static uint NativeCallbackHandler(
IntPtr listener,
IntPtr context,
ref ListenerEvent connectionEventStruct)
{
GCHandle handle = GCHandle.FromIntPtr(context);
MsQuicListener quicListener = (MsQuicListener)handle.Target;
return quicListener.ListenerCallbackHandler(ref connectionEventStruct);
}
internal void SetCallbackHandler()
{
_handle = GCHandle.Alloc(this);
_listenerDelegate = new ListenerCallbackDelegate(NativeCallbackHandler);
MsQuicApi.Api.SetCallbackHandlerDelegate(
_ptr,
_listenerDelegate,
GCHandle.ToIntPtr(_handle));
}
private void ThrowIfDisposed()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(MsQuicStream));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,36 @@
// 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.Threading;
using System.Threading.Tasks;
namespace System.Net.Quic.Implementations
{
internal abstract class QuicConnectionProvider : IDisposable
{
internal abstract bool Connected { get; }
internal abstract IPEndPoint LocalEndPoint { get; }
internal abstract IPEndPoint RemoteEndPoint { get; }
internal abstract ValueTask ConnectAsync(CancellationToken cancellationToken = default);
internal abstract QuicStreamProvider OpenUnidirectionalStream();
internal abstract QuicStreamProvider OpenBidirectionalStream();
internal abstract long GetRemoteAvailableUnidirectionalStreamCount();
internal abstract long GetRemoteAvailableBidirectionalStreamCount();
internal abstract ValueTask<QuicStreamProvider> AcceptStreamAsync(CancellationToken cancellationToken = default);
internal abstract System.Net.Security.SslApplicationProtocol NegotiatedApplicationProtocol { get; }
internal abstract ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default);
public abstract void Dispose();
}
}

View File

@ -0,0 +1,17 @@
// 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
{
internal abstract class QuicImplementationProvider
{
internal QuicImplementationProvider() { }
internal abstract QuicListenerProvider CreateListener(QuicListenerOptions options);
internal abstract QuicConnectionProvider CreateConnection(QuicClientConnectionOptions options);
}
}

View File

@ -0,0 +1,22 @@
// 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.Threading;
using System.Threading.Tasks;
namespace System.Net.Quic.Implementations
{
internal abstract class QuicListenerProvider : IDisposable
{
internal abstract IPEndPoint ListenEndPoint { get; }
internal abstract ValueTask<QuicConnectionProvider> AcceptConnectionAsync(CancellationToken cancellationToken = default);
internal abstract void Start();
internal abstract void Close();
public abstract void Dispose();
}
}

View File

@ -0,0 +1,53 @@
// 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.Threading;
using System.Threading.Tasks;
namespace System.Net.Quic.Implementations
{
internal abstract class QuicStreamProvider : IDisposable, IAsyncDisposable
{
internal abstract long StreamId { get; }
internal abstract bool CanRead { get; }
internal abstract int Read(Span<byte> buffer);
internal abstract ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
internal abstract void AbortRead(long errorCode);
internal abstract void AbortWrite(long errorCode);
internal abstract bool CanWrite { get; }
internal abstract void Write(ReadOnlySpan<byte> buffer);
internal abstract ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default);
internal abstract ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool endStream, CancellationToken cancellationToken = default);
internal abstract ValueTask WriteAsync(ReadOnlySequence<byte> buffers, CancellationToken cancellationToken = default);
internal abstract ValueTask WriteAsync(ReadOnlySequence<byte> buffers, bool endStream, CancellationToken cancellationToken = default);
internal abstract ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, CancellationToken cancellationToken = default);
internal abstract ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, bool endStream, CancellationToken cancellationToken = default);
internal abstract ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default);
internal abstract void Shutdown();
internal abstract void Flush();
internal abstract Task FlushAsync(CancellationToken cancellationToken);
public abstract void Dispose();
public abstract ValueTask DisposeAsync();
}
}

View File

@ -0,0 +1,16 @@
// 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;
using System.Net.Quic.Implementations.MsQuic.Internal;
using System.Runtime.InteropServices;
internal static partial class Interop
{
internal static class MsQuic
{
[DllImport(Libraries.MsQuic)]
internal static unsafe extern uint MsQuicOpen(int version, out MsQuicNativeMethods.NativeApi* registration);
}
}

View File

@ -0,0 +1,167 @@
// 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.
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
/// <summary>
/// Flags to pass when creating a security config.
/// </summary>
[Flags]
internal enum QUIC_SEC_CONFIG_FLAG : uint
{
NONE = 0,
CERT_HASH = 0x00000001,
CERT_HASH_STORE = 0x00000002,
CERT_CONTEXT = 0x00000004,
CERT_FILE = 0x00000008,
ENABL_OCSP = 0x00000010,
CERT_NULL = 0xF0000000,
}
[Flags]
internal enum QUIC_CONNECTION_SHUTDOWN_FLAG : uint
{
NONE = 0x0,
SILENT = 0x1
}
[Flags]
internal enum QUIC_STREAM_OPEN_FLAG : uint
{
NONE = 0,
UNIDIRECTIONAL = 0x1,
ZERO_RTT = 0x2,
}
[Flags]
internal enum QUIC_STREAM_START_FLAG : uint
{
NONE = 0,
FAIL_BLOCKED = 0x1,
IMMEDIATE = 0x2,
ASYNC = 0x4,
}
[Flags]
internal enum QUIC_STREAM_SHUTDOWN_FLAG : uint
{
NONE = 0,
GRACEFUL = 0x1,
ABORT_SEND = 0x2,
ABORT_RECV = 0x4,
ABORT = ABORT_SEND | ABORT_RECV,
IMMEDIATE = 0x8
}
[Flags]
internal enum QUIC_RECEIVE_FLAG : uint
{
NONE = 0,
ZERO_RTT = 0x1,
FIN = 0x02
}
[Flags]
internal enum QUIC_SEND_FLAG : uint
{
NONE = 0,
ALLOW_0_RTT = 0x00000001,
FIN = 0x00000002,
}
internal enum QUIC_PARAM_LEVEL : uint
{
REGISTRATION = 0,
SESSION = 1,
LISTENER = 2,
CONNECTION = 3,
TLS = 4,
STREAM = 5,
}
internal enum QUIC_PARAM_REGISTRATION : uint
{
RETRY_MEMORY_PERCENT = 0,
CID_PREFIX = 1
}
internal enum QUIC_PARAM_SESSION : uint
{
TLS_TICKET_KEY = 0,
PEER_BIDI_STREAM_COUNT = 1,
PEER_UNIDI_STREAM_COUNT = 2,
IDLE_TIMEOUT = 3,
DISCONNECT_TIMEOUT = 4,
MAX_BYTES_PER_KEY = 5
}
internal enum QUIC_PARAM_LISTENER : uint
{
LOCAL_ADDRESS = 0,
STATS = 1
}
internal enum QUIC_PARAM_CONN : uint
{
QUIC_VERSION = 0,
LOCAL_ADDRESS = 1,
REMOTE_ADDRESS = 2,
IDLE_TIMEOUT = 3,
PEER_BIDI_STREAM_COUNT = 4,
PEER_UNIDI_STREAM_COUNT = 5,
LOCAL_BIDI_STREAM_COUNT = 6,
LOCAL_UNIDI_STREAM_COUNT = 7,
CLOSE_REASON_PHRASE = 8,
STATISTICS = 9,
STATISTICS_PLAT = 10,
CERT_VALIDATION_FLAGS = 11,
KEEP_ALIVE = 12,
DISCONNECT_TIMEOUT = 13,
SEC_CONFIG = 14,
SEND_BUFFERING = 15,
SEND_PACING = 16,
SHARE_UDP_BINDING = 17,
IDEAL_PROCESSOR = 18,
MAX_STREAM_IDS = 19
}
internal enum QUIC_PARAM_STREAM : uint
{
ID = 0,
ZERORTT_LENGTH = 1,
IDEAL_SEND_BUFFER = 2
}
internal enum QUIC_LISTENER_EVENT : uint
{
NEW_CONNECTION = 0
}
internal enum QUIC_CONNECTION_EVENT : uint
{
CONNECTED = 0,
SHUTDOWN_INITIATED_BY_TRANSPORT = 1,
SHUTDOWN_INITIATED_BY_PEER = 2,
SHUTDOWN_COMPLETE = 3,
LOCAL_ADDRESS_CHANGED = 4,
PEER_ADDRESS_CHANGED = 5,
PEER_STREAM_STARTED = 6,
STREAMS_AVAILABLE = 7,
PEER_NEEDS_STREAMS = 8,
IDEAL_PROCESSOR_CHANGED = 9,
}
internal enum QUIC_STREAM_EVENT : uint
{
START_COMPLETE = 0,
RECEIVE = 1,
SEND_COMPLETE = 2,
PEER_SEND_SHUTDOWN = 3,
PEER_SEND_ABORTED = 4,
PEER_RECEIVE_ABORTED = 5,
SEND_SHUTDOWN_COMPLETE = 6,
SHUTDOWN_COMPLETE = 7,
IDEAL_SEND_BUFFER_SIZE = 8,
}
}

View File

@ -0,0 +1,488 @@
// 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.Runtime.InteropServices;
using System.Text;
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
/// <summary>
/// Contains all native delegates and structs that are used with MsQuic.
/// </summary>
internal static unsafe class MsQuicNativeMethods
{
[StructLayout(LayoutKind.Sequential)]
internal struct NativeApi
{
internal uint Version;
internal IntPtr SetContext;
internal IntPtr GetContext;
internal IntPtr SetCallbackHandler;
internal IntPtr SetParam;
internal IntPtr GetParam;
internal IntPtr RegistrationOpen;
internal IntPtr RegistrationClose;
internal IntPtr SecConfigCreate;
internal IntPtr SecConfigDelete;
internal IntPtr SessionOpen;
internal IntPtr SessionClose;
internal IntPtr SessionShutdown;
internal IntPtr ListenerOpen;
internal IntPtr ListenerClose;
internal IntPtr ListenerStart;
internal IntPtr ListenerStop;
internal IntPtr ConnectionOpen;
internal IntPtr ConnectionClose;
internal IntPtr ConnectionShutdown;
internal IntPtr ConnectionStart;
internal IntPtr StreamOpen;
internal IntPtr StreamClose;
internal IntPtr StreamStart;
internal IntPtr StreamShutdown;
internal IntPtr StreamSend;
internal IntPtr StreamReceiveComplete;
internal IntPtr StreamReceiveSetEnabled;
}
internal delegate uint SetContextDelegate(
IntPtr handle,
IntPtr context);
internal delegate IntPtr GetContextDelegate(
IntPtr handle);
internal delegate void SetCallbackHandlerDelegate(
IntPtr handle,
Delegate del,
IntPtr context);
internal delegate uint SetParamDelegate(
IntPtr handle,
uint level,
uint param,
uint bufferLength,
byte* buffer);
internal delegate uint GetParamDelegate(
IntPtr handle,
uint level,
uint param,
uint* bufferLength,
byte* buffer);
internal delegate uint RegistrationOpenDelegate(byte[] appName, out IntPtr registrationContext);
internal delegate void RegistrationCloseDelegate(IntPtr registrationContext);
internal delegate void SecConfigCreateCompleteDelegate(IntPtr context, uint status, IntPtr securityConfig);
internal delegate uint SecConfigCreateDelegate(
IntPtr registrationContext,
uint flags,
IntPtr certificate,
[MarshalAs(UnmanagedType.LPStr)]string principal,
IntPtr context,
SecConfigCreateCompleteDelegate completionHandler);
internal delegate void SecConfigDeleteDelegate(
IntPtr securityConfig);
internal delegate uint SessionOpenDelegate(
IntPtr registrationContext,
byte[] utf8String,
IntPtr context,
ref IntPtr session);
internal delegate void SessionCloseDelegate(
IntPtr session);
internal delegate void SessionShutdownDelegate(
IntPtr session,
uint flags,
ushort errorCode);
[StructLayout(LayoutKind.Sequential)]
internal struct ListenerEvent
{
internal QUIC_LISTENER_EVENT Type;
internal ListenerEventDataUnion Data;
}
[StructLayout(LayoutKind.Explicit)]
internal struct ListenerEventDataUnion
{
[FieldOffset(0)]
internal ListenerEventDataNewConnection NewConnection;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ListenerEventDataNewConnection
{
internal IntPtr Info;
internal IntPtr Connection;
internal IntPtr SecurityConfig;
}
[StructLayout(LayoutKind.Sequential)]
internal struct NewConnectionInfo
{
internal uint QuicVersion;
internal IntPtr LocalAddress;
internal IntPtr RemoteAddress;
internal ushort CryptoBufferLength;
internal ushort AlpnListLength;
internal ushort ServerNameLength;
internal IntPtr CryptoBuffer;
internal IntPtr AlpnList;
internal IntPtr ServerName;
}
internal delegate uint ListenerCallbackDelegate(
IntPtr listener,
IntPtr context,
ref ListenerEvent evt);
internal delegate uint ListenerOpenDelegate(
IntPtr session,
ListenerCallbackDelegate handler,
IntPtr context,
out IntPtr listener);
internal delegate uint ListenerCloseDelegate(
IntPtr listener);
internal delegate uint ListenerStartDelegate(
IntPtr listener,
ref SOCKADDR_INET localAddress);
internal delegate uint ListenerStopDelegate(
IntPtr listener);
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataConnected
{
internal bool EarlyDataAccepted;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataShutdownBegin
{
internal uint Status;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataShutdownBeginPeer
{
internal long ErrorCode;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataShutdownComplete
{
internal bool TimedOut;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataLocalAddrChanged
{
internal IntPtr Address;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataPeerAddrChanged
{
internal IntPtr Address;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataNewStream
{
internal IntPtr Stream;
internal QUIC_STREAM_OPEN_FLAG Flags;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataStreamsAvailable
{
internal ushort BiDirectionalCount;
internal ushort UniDirectionalCount;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEventDataIdealSendBuffer
{
internal ulong NumBytes;
}
[StructLayout(LayoutKind.Explicit)]
internal struct ConnectionEventDataUnion
{
[FieldOffset(0)]
internal ConnectionEventDataConnected Connected;
[FieldOffset(0)]
internal ConnectionEventDataShutdownBegin ShutdownBegin;
[FieldOffset(0)]
internal ConnectionEventDataShutdownBeginPeer ShutdownBeginPeer;
[FieldOffset(0)]
internal ConnectionEventDataShutdownComplete ShutdownComplete;
[FieldOffset(0)]
internal ConnectionEventDataLocalAddrChanged LocalAddrChanged;
[FieldOffset(0)]
internal ConnectionEventDataPeerAddrChanged PeerAddrChanged;
[FieldOffset(0)]
internal ConnectionEventDataNewStream NewStream;
[FieldOffset(0)]
internal ConnectionEventDataStreamsAvailable StreamsAvailable;
[FieldOffset(0)]
internal ConnectionEventDataIdealSendBuffer IdealSendBuffer;
}
[StructLayout(LayoutKind.Sequential)]
internal struct ConnectionEvent
{
internal QUIC_CONNECTION_EVENT Type;
internal ConnectionEventDataUnion Data;
internal bool EarlyDataAccepted => Data.Connected.EarlyDataAccepted;
internal ulong NumBytes => Data.IdealSendBuffer.NumBytes;
internal uint ShutdownBeginStatus => Data.ShutdownBegin.Status;
internal long ShutdownBeginPeerStatus => Data.ShutdownBeginPeer.ErrorCode;
internal bool ShutdownTimedOut => Data.ShutdownComplete.TimedOut;
internal ushort BiDirectionalCount => Data.StreamsAvailable.BiDirectionalCount;
internal ushort UniDirectionalCount => Data.StreamsAvailable.UniDirectionalCount;
internal QUIC_STREAM_OPEN_FLAG StreamFlags => Data.NewStream.Flags;
}
internal delegate uint ConnectionCallbackDelegate(
IntPtr connection,
IntPtr context,
ref ConnectionEvent connectionEvent);
internal delegate uint ConnectionOpenDelegate(
IntPtr session,
ConnectionCallbackDelegate handler,
IntPtr context,
out IntPtr connection);
internal delegate uint ConnectionCloseDelegate(
IntPtr connection);
internal delegate uint ConnectionStartDelegate(
IntPtr connection,
ushort family,
[MarshalAs(UnmanagedType.LPStr)]
string serverName,
ushort serverPort);
internal delegate uint ConnectionShutdownDelegate(
IntPtr connection,
uint flags,
long errorCode);
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataRecv
{
internal ulong AbsoluteOffset;
internal ulong TotalBufferLength;
internal QuicBuffer* Buffers;
internal uint BufferCount;
internal uint Flags;
}
[StructLayout(LayoutKind.Explicit)]
internal struct StreamEventDataSendComplete
{
[FieldOffset(0)]
internal byte Canceled;
[FieldOffset(1)]
internal IntPtr ClientContext;
internal bool IsCanceled()
{
return Canceled != 0;
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataPeerSendAbort
{
internal long ErrorCode;
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataPeerRecvAbort
{
internal long ErrorCode;
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEventDataSendShutdownComplete
{
internal byte Graceful;
}
[StructLayout(LayoutKind.Explicit)]
internal struct StreamEventDataUnion
{
[FieldOffset(0)]
internal StreamEventDataRecv Recv;
[FieldOffset(0)]
internal StreamEventDataSendComplete SendComplete;
[FieldOffset(0)]
internal StreamEventDataPeerSendAbort PeerSendAbort;
[FieldOffset(0)]
internal StreamEventDataPeerRecvAbort PeerRecvAbort;
[FieldOffset(0)]
internal StreamEventDataSendShutdownComplete SendShutdownComplete;
}
[StructLayout(LayoutKind.Sequential)]
internal struct StreamEvent
{
internal QUIC_STREAM_EVENT Type;
internal StreamEventDataUnion Data;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SOCKADDR_IN
{
internal ushort sin_family;
internal ushort sin_port;
internal byte sin_addr0;
internal byte sin_addr1;
internal byte sin_addr2;
internal byte sin_addr3;
internal byte[] Address
{
get
{
return new byte[] { sin_addr0, sin_addr1, sin_addr2, sin_addr3 };
}
}
}
[StructLayout(LayoutKind.Sequential)]
internal struct SOCKADDR_IN6
{
internal ushort _family;
internal ushort _port;
internal uint _flowinfo;
internal byte _addr0;
internal byte _addr1;
internal byte _addr2;
internal byte _addr3;
internal byte _addr4;
internal byte _addr5;
internal byte _addr6;
internal byte _addr7;
internal byte _addr8;
internal byte _addr9;
internal byte _addr10;
internal byte _addr11;
internal byte _addr12;
internal byte _addr13;
internal byte _addr14;
internal byte _addr15;
internal uint _scope_id;
internal byte[] Address
{
get
{
return new byte[] {
_addr0, _addr1, _addr2, _addr3,
_addr4, _addr5, _addr6, _addr7,
_addr8, _addr9, _addr10, _addr11,
_addr12, _addr13, _addr14, _addr15 };
}
}
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)]
internal struct SOCKADDR_INET
{
[FieldOffset(0)]
internal SOCKADDR_IN Ipv4;
[FieldOffset(0)]
internal SOCKADDR_IN6 Ipv6;
[FieldOffset(0)]
internal ushort si_family;
}
internal delegate uint StreamCallbackDelegate(
IntPtr stream,
IntPtr context,
ref StreamEvent streamEvent);
internal delegate uint StreamOpenDelegate(
IntPtr connection,
uint flags,
StreamCallbackDelegate handler,
IntPtr context,
out IntPtr stream);
internal delegate uint StreamStartDelegate(
IntPtr stream,
uint flags);
internal delegate uint StreamCloseDelegate(
IntPtr stream);
internal delegate uint StreamShutdownDelegate(
IntPtr stream,
uint flags,
long errorCode);
internal delegate uint StreamSendDelegate(
IntPtr stream,
QuicBuffer* buffers,
uint bufferCount,
uint flags,
IntPtr clientSendContext);
internal delegate uint StreamReceiveCompleteDelegate(
IntPtr stream,
ulong bufferLength);
internal delegate uint StreamReceiveSetEnabledDelegate(
IntPtr stream,
bool enabled);
[StructLayout(LayoutKind.Sequential)]
internal unsafe struct QuicBuffer
{
internal uint Length;
internal byte* Buffer;
}
[StructLayout(LayoutKind.Sequential)]
internal struct CertFileParams
{
internal IntPtr CertificateFilePath;
internal IntPtr PrivateKeyFilePath;
}
}
}

View File

@ -0,0 +1,121 @@
// 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.Runtime.InteropServices;
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
internal static class MsQuicStatusCodes
{
internal static readonly uint Success = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.Success : Linux.Success;
internal static readonly uint Pending = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.Pending : Linux.Pending;
internal static readonly uint InternalError = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Windows.InternalError : Linux.InternalError;
// TODO return better error messages here.
public static string GetError(uint status)
{
return RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Windows.GetError(status) : Linux.GetError(status);
}
private static class Windows
{
internal const uint Success = 0;
internal const uint Pending = 0x703E5;
internal const uint Continue = 0x704DE;
internal const uint OutOfMemory = 0x8007000E;
internal const uint InvalidParameter = 0x80070057;
internal const uint InvalidState = 0x8007139F;
internal const uint NotSupported = 0x80004002;
internal const uint NotFound = 0x80070490;
internal const uint BufferTooSmall = 0x8007007A;
internal const uint HandshakeFailure = 0x80410000;
internal const uint Aborted = 0x80004004;
internal const uint AddressInUse = 0x80072740;
internal const uint ConnectionTimeout = 0x800704CF;
internal const uint ConnectionIdle = 0x800704D4;
internal const uint InternalError = 0x80004005;
internal const uint ServerBusy = 0x800704C9;
internal const uint ProtocolError = 0x800704CD;
internal const uint HostUnreachable = 0x800704D0;
internal const uint VerNegError = 0x80410001;
// TODO return better error messages here.
public static string GetError(uint status)
{
return status switch
{
Success => "SUCCESS",
Pending => "PENDING",
Continue => "CONTINUE",
OutOfMemory => "OUT_OF_MEMORY",
InvalidParameter => "INVALID_PARAMETER",
InvalidState => "INVALID_STATE",
NotSupported => "NOT_SUPPORTED",
NotFound => "NOT_FOUND",
BufferTooSmall => "BUFFER_TOO_SMALL",
HandshakeFailure => "HANDSHAKE_FAILURE",
Aborted => "ABORTED",
AddressInUse => "ADDRESS_IN_USE",
ConnectionTimeout => "CONNECTION_TIMEOUT",
ConnectionIdle => "CONNECTION_IDLE",
InternalError => "INTERNAL_ERROR",
ServerBusy => "SERVER_BUSY",
ProtocolError => "PROTOCOL_ERROR",
VerNegError => "VER_NEG_ERROR",
_ => status.ToString()
};
}
}
private static class Linux
{
internal const uint Success = 0;
internal const uint Pending = unchecked((uint)-2);
internal const uint Continue = unchecked((uint)-1);
internal const uint OutOfMemory = 12;
internal const uint InvalidParameter = 22;
internal const uint InvalidState = 200000002;
internal const uint NotSupported = 95;
internal const uint NotFound = 2;
internal const uint BufferTooSmall = 75;
internal const uint HandshakeFailure = 200000009;
internal const uint Aborted = 200000008;
internal const uint AddressInUse = 98;
internal const uint ConnectionTimeout = 110;
internal const uint ConnectionIdle = 200000011;
internal const uint InternalError = 200000012;
internal const uint ServerBusy = 200000007;
internal const uint ProtocolError = 200000013;
internal const uint VerNegError = 200000014;
// TODO return better error messages here.
public static string GetError(uint status)
{
return status switch
{
Success => "SUCCESS",
Pending => "PENDING",
Continue => "CONTINUE",
OutOfMemory => "OUT_OF_MEMORY",
InvalidParameter => "INVALID_PARAMETER",
InvalidState => "INVALID_STATE",
NotSupported => "NOT_SUPPORTED",
NotFound => "NOT_FOUND",
BufferTooSmall => "BUFFER_TOO_SMALL",
HandshakeFailure => "HANDSHAKE_FAILURE",
Aborted => "ABORTED",
AddressInUse => "ADDRESS_IN_USE",
ConnectionTimeout => "CONNECTION_TIMEOUT",
ConnectionIdle => "CONNECTION_IDLE",
InternalError => "INTERNAL_ERROR",
ServerBusy => "SERVER_BUSY",
ProtocolError => "PROTOCOL_ERROR",
VerNegError => "VER_NEG_ERROR",
_ => status.ToString()
};
}
}
}
}

View File

@ -0,0 +1,26 @@
// 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.Runtime.InteropServices;
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
internal static class MsQuicStatusHelper
{
internal static bool SuccessfulStatusCode(uint status)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
return status < 0x80000000;
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return (int)status <= 0;
}
return false;
}
}
}

View File

@ -0,0 +1,13 @@
// 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.Diagnostics.Tracing;
namespace System.Net
{
[EventSource(Name = "Microsoft-System-Net-Quic")]
internal sealed partial class NetEventSource : EventSource
{
}
}

View File

@ -0,0 +1,50 @@
// 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
{
/// <summary>
/// Options to provide to the <see cref="QuicConnection"/> when connecting to a Listener.
/// </summary>
internal class QuicClientConnectionOptions
{
/// <summary>
/// Client authentication options to use when establishing a <see cref="QuicConnection"/>.
/// </summary>
public SslClientAuthenticationOptions ClientAuthenticationOptions { get; set; }
/// <summary>
/// The local endpoint that will be bound to.
/// </summary>
public IPEndPoint LocalEndPoint { get; set; }
/// <summary>
/// The endpoint to connect to.
/// </summary>
public IPEndPoint RemoteEndPoint { get; set; }
/// <summary>
/// Limit on the number of bidirectional streams the peer connection can create
/// on an accepted connection.
/// Default is 100.
/// </summary>
// TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
public long MaxBidirectionalStreams { get; set; } = 100;
/// <summary>
/// Limit on the number of unidirectional streams the peer connection can create
/// on an accepted connection.
/// Default is 100.
/// </summary>
// TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
public long MaxUnidirectionalStreams { get; set; } = 100;
/// <summary>
/// Idle timeout for connections, afterwhich the connection will be closed.
/// </summary>
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(2);
}
}

View File

@ -0,0 +1,99 @@
// 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.Quic.Implementations;
using System.Net.Quic.Implementations.MsQuic.Internal;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Quic
{
internal sealed class QuicConnection : IDisposable
{
private readonly QuicConnectionProvider _provider;
public static bool IsQuicSupported => MsQuicApi.IsQuicSupported;
/// <summary>
/// Create an outbound QUIC connection.
/// </summary>
/// <param name="remoteEndPoint">The remote endpoint to connect to.</param>
/// <param name="sslClientAuthenticationOptions">TLS options</param>
/// <param name="localEndPoint">The local endpoint to connect from.</param>
public QuicConnection(IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null)
: this(QuicImplementationProviders.Default, remoteEndPoint, sslClientAuthenticationOptions, localEndPoint)
{
}
// !!! TEMPORARY: Remove "implementationProvider" before shipping
public QuicConnection(QuicImplementationProvider implementationProvider, IPEndPoint remoteEndPoint, SslClientAuthenticationOptions sslClientAuthenticationOptions, IPEndPoint localEndPoint = null)
: this(implementationProvider, new QuicClientConnectionOptions() { RemoteEndPoint = remoteEndPoint, ClientAuthenticationOptions = sslClientAuthenticationOptions, LocalEndPoint = localEndPoint })
{
}
public QuicConnection(QuicImplementationProvider implementationProvider, QuicClientConnectionOptions options)
{
_provider = implementationProvider.CreateConnection(options);
}
internal QuicConnection(QuicConnectionProvider provider)
{
_provider = provider;
}
/// <summary>
/// Indicates whether the QuicConnection is connected.
/// </summary>
public bool Connected => _provider.Connected;
public IPEndPoint LocalEndPoint => _provider.LocalEndPoint;
public IPEndPoint RemoteEndPoint => _provider.RemoteEndPoint;
public SslApplicationProtocol NegotiatedApplicationProtocol => _provider.NegotiatedApplicationProtocol;
/// <summary>
/// Connect to the remote endpoint.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public ValueTask ConnectAsync(CancellationToken cancellationToken = default) => _provider.ConnectAsync(cancellationToken);
/// <summary>
/// Create an outbound unidirectional stream.
/// </summary>
/// <returns></returns>
public QuicStream OpenUnidirectionalStream() => new QuicStream(_provider.OpenUnidirectionalStream());
/// <summary>
/// Create an outbound bidirectional stream.
/// </summary>
/// <returns></returns>
public QuicStream OpenBidirectionalStream() => new QuicStream(_provider.OpenBidirectionalStream());
/// <summary>
/// Accept an incoming stream.
/// </summary>
/// <returns></returns>
public async ValueTask<QuicStream> AcceptStreamAsync(CancellationToken cancellationToken = default) => new QuicStream(await _provider.AcceptStreamAsync(cancellationToken).ConfigureAwait(false));
/// <summary>
/// Close the connection and terminate any active streams.
/// </summary>
public ValueTask CloseAsync(long errorCode, CancellationToken cancellationToken = default) => _provider.CloseAsync(errorCode, cancellationToken);
public void Dispose() => _provider.Dispose();
/// <summary>
/// Gets the maximum number of bidirectional streams that can be made to the peer.
/// </summary>
public long GetRemoteAvailableUnidirectionalStreamCount() => _provider.GetRemoteAvailableUnidirectionalStreamCount();
/// <summary>
/// Gets the maximum number of unidirectional streams that can be made to the peer.
/// </summary>
public long GetRemoteAvailableBidirectionalStreamCount() => _provider.GetRemoteAvailableBidirectionalStreamCount();
}
}

View File

@ -0,0 +1,22 @@
// 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.
namespace System.Net.Quic
{
internal 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; }
}
}

View File

@ -0,0 +1,14 @@
// 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.
namespace System.Net.Quic
{
internal class QuicException : Exception
{
public QuicException(string message)
: base (message)
{
}
}
}

View File

@ -0,0 +1,13 @@
// 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.
namespace System.Net.Quic
{
internal static class QuicImplementationProviders
{
public static Implementations.QuicImplementationProvider Mock { get; } = new Implementations.Mock.MockImplementationProvider();
public static Implementations.QuicImplementationProvider MsQuic { get; } = new Implementations.MsQuic.MsQuicImplementationProvider();
public static Implementations.QuicImplementationProvider Default => MsQuic;
}
}

View File

@ -0,0 +1,55 @@
// 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.Quic.Implementations;
using System.Net.Security;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Quic
{
internal sealed class QuicListener : IDisposable
{
private readonly QuicListenerProvider _provider;
/// <summary>
/// Create a QUIC listener on the specified local endpoint and start listening.
/// </summary>
/// <param name="listenEndPoint">The local endpoint to listen on.</param>
/// <param name="sslServerAuthenticationOptions">TLS options for the listener.</param>
public QuicListener(IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
: this(QuicImplementationProviders.Default, listenEndPoint, sslServerAuthenticationOptions)
{
}
// !!! TEMPORARY: Remove "implementationProvider" before shipping
public QuicListener(QuicImplementationProvider implementationProvider, IPEndPoint listenEndPoint, SslServerAuthenticationOptions sslServerAuthenticationOptions)
: this(implementationProvider, new QuicListenerOptions() { ListenEndPoint = listenEndPoint, ServerAuthenticationOptions = sslServerAuthenticationOptions })
{
}
public QuicListener(QuicImplementationProvider implementationProvider, QuicListenerOptions options)
{
_provider = implementationProvider.CreateListener(options);
}
public IPEndPoint ListenEndPoint => _provider.ListenEndPoint;
/// <summary>
/// Accept a connection.
/// </summary>
/// <returns></returns>
public async ValueTask<QuicConnection> AcceptConnectionAsync(CancellationToken cancellationToken = default) =>
new QuicConnection(await _provider.AcceptConnectionAsync(cancellationToken).ConfigureAwait(false));
public void Start() => _provider.Start();
/// <summary>
/// Stop listening and close the listener.
/// </summary>
public void Close() => _provider.Close();
public void Dispose() => _provider.Dispose();
}
}

View File

@ -0,0 +1,59 @@
// 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
{
/// <summary>
/// Options to provide to the <see cref="QuicListener"/>.
/// </summary>
internal class QuicListenerOptions
{
/// <summary>
/// Server Ssl options to use for ALPN, SNI, etc.
/// </summary>
public SslServerAuthenticationOptions ServerAuthenticationOptions { get; set; }
/// <summary>
/// Optional path to certificate file to configure the security configuration.
/// </summary>
public string CertificateFilePath { get; set; }
/// <summary>
/// Optional path to private key file to configure the security configuration.
/// </summary>
public string PrivateKeyFilePath { get; set; }
/// <summary>
/// The endpoint to listen on.
/// </summary>
public IPEndPoint ListenEndPoint { get; set; }
/// <summary>
/// Number of connections to be held without accepting the connection.
/// </summary>
public int ListenBacklog { get; set; } = 512;
/// <summary>
/// Limit on the number of bidirectional streams an accepted connection can create
/// back to the client.
/// Default is 100.
/// </summary>
// TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
public long MaxBidirectionalStreams { get; set; } = 100;
/// <summary>
/// Limit on the number of unidirectional streams the peer connection can create.
/// Default is 100.
/// </summary>
// TODO consider constraining these limits to 0 to whatever the max of the QUIC library we are using.
public long MaxUnidirectionalStreams { get; set; } = 100;
/// <summary>
/// Idle timeout for connections, afterwhich the connection will be closed.
/// </summary>
public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(10);
}
}

View File

@ -0,0 +1,18 @@
// 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.
namespace System.Net.Quic
{
internal class QuicOperationAbortedException : QuicException
{
internal QuicOperationAbortedException()
: base(SR.net_quic_operationaborted)
{
}
public QuicOperationAbortedException(string message) : base(message)
{
}
}
}

View File

@ -0,0 +1,133 @@
// 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.IO;
using System.Net.Quic.Implementations;
using System.Threading;
using System.Threading.Tasks;
namespace System.Net.Quic
{
internal sealed class QuicStream : Stream
{
private readonly QuicStreamProvider _provider;
internal QuicStream(QuicStreamProvider provider)
{
_provider = provider;
}
//
// Boilerplate implementation stuff
//
public override bool CanSeek => false;
public override long Length => throw new NotSupportedException();
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) =>
TaskToApm.Begin(ReadAsync(buffer, offset, count, default), callback, state);
public override int EndRead(IAsyncResult asyncResult) =>
TaskToApm.End<int>(asyncResult);
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) =>
TaskToApm.Begin(WriteAsync(buffer, offset, count, default), callback, state);
public override void EndWrite(IAsyncResult asyncResult) =>
TaskToApm.End(asyncResult);
private static void ValidateBufferArgs(byte[] buffer, int offset, int count)
{
if (buffer == null)
{
throw new ArgumentNullException(nameof(buffer));
}
if ((uint)offset > buffer.Length)
{
throw new ArgumentOutOfRangeException(nameof(offset));
}
if ((uint)count > buffer.Length - offset)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
}
public override int Read(byte[] buffer, int offset, int count)
{
ValidateBufferArgs(buffer, offset, count);
return Read(buffer.AsSpan(offset, count));
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArgs(buffer, offset, count);
return ReadAsync(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
public override void Write(byte[] buffer, int offset, int count)
{
ValidateBufferArgs(buffer, offset, count);
Write(buffer.AsSpan(offset, count));
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
ValidateBufferArgs(buffer, offset, count);
return WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
/// <summary>
/// QUIC stream ID.
/// </summary>
public long StreamId => _provider.StreamId;
public override bool CanRead => _provider.CanRead;
public override int Read(Span<byte> buffer) => _provider.Read(buffer);
public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default) => _provider.ReadAsync(buffer, cancellationToken);
public override bool CanWrite => _provider.CanWrite;
public override void Write(ReadOnlySpan<byte> buffer) => _provider.Write(buffer);
public override ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffer, cancellationToken);
public override void Flush() => _provider.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) => _provider.FlushAsync(cancellationToken);
public void AbortRead(long errorCode) => _provider.AbortRead(errorCode);
public void AbortWrite(long errorCode) => _provider.AbortWrite(errorCode);
public ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, bool endStream, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffer, endStream, cancellationToken);
public ValueTask WriteAsync(ReadOnlySequence<byte> buffers, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, cancellationToken);
public ValueTask WriteAsync(ReadOnlySequence<byte> buffers, bool endStream, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, endStream, cancellationToken);
public ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, cancellationToken);
public ValueTask WriteAsync(ReadOnlyMemory<ReadOnlyMemory<byte>> buffers, bool endStream, CancellationToken cancellationToken = default) => _provider.WriteAsync(buffers, endStream, cancellationToken);
public ValueTask ShutdownWriteCompleted(CancellationToken cancellationToken = default) => _provider.ShutdownWriteCompleted(cancellationToken);
public void Shutdown() => _provider.Shutdown();
protected override void Dispose(bool disposing)
{
if (disposing)
{
_provider.Dispose();
}
}
}
}

View File

@ -0,0 +1,22 @@
// 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.
namespace System.Net.Quic
{
internal 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; }
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace System.Net.Quic
{
internal static partial class SR
{
// The resource generator used in AspNetCore does not create this method. This file fills in that functional gap
// so we don't have to modify the shared source.
internal static string Format(string resourceFormat, params object[] args)
{
if (args != null)
{
return string.Format(resourceFormat, args);
}
return resourceFormat;
}
}
}

View File

@ -156,4 +156,16 @@
<data name="net_http_request_invalid_char_encoding" xml:space="preserve">
<value>Request headers must contain only ASCII characters.</value>
</data>
<data name="net_quic_connectionaborted" xml:space="preserve">
<value>Connection aborted by peer ({0}).</value>
</data>
<data name="net_quic_notsupported" xml:space="preserve">
<value>QUIC is not supported on this platform. See http://aka.ms/dotnetquic</value>
</data>
<data name="net_quic_operationaborted" xml:space="preserve">
<value>Operation aborted.</value>
</data>
<data name="net_quic_streamaborted" xml:space="preserve">
<value>Stream aborted by peer ({0}).</value>
</data>
</root>

View File

@ -4,7 +4,7 @@
<TargetFrameworks>$(DefaultNetCoreTargetFramework)</TargetFrameworks>
<DebugType>portable</DebugType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>CS0649</NoWarn><!-- Not all APIs are called in the shared test project -->
<NoWarn>CS0649;CS0436</NoWarn><!-- Not all APIs are called in the shared test project --><!-- Conflicts between internal and public Quic APIs -->
</PropertyGroup>
<ItemGroup>
@ -15,6 +15,9 @@
<Compile Include="$(SharedSourceRoot)runtime\**\*.cs" Link="Shared\runtime\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)HashCodeCombiner\**\*.cs" Link="Shared\HashCodeCombiner\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)HostFactoryResolver\**\*.cs" Link="Shared\HostFactoryResolver\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)runtime\*.cs" Link="Shared\runtime\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)runtime\Http2\**\*.cs" Link="Shared\runtime\Http2\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)runtime\Http3\**\*.cs" Link="Shared\runtime\Http3\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)HttpSys\**\*.cs" Link="Shared\HttpSys\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)NonCapturingTimer\*.cs" Link="Shared\NonCapturingTimer\%(Filename)%(Extension)"/>
<Compile Include="$(SharedSourceRoot)ObjectMethodExecutor\*.cs" Link="Shared\ObjectMethodExecutor\%(Filename)%(Extension)"/>
@ -56,4 +59,4 @@
</EmbeddedResource>
</ItemGroup>
</Project>
</Project>

View File

@ -11,6 +11,9 @@
<IsTestProject>true</IsTestProject>
<!-- Installing Java on ARM will take some work -->
<SkipHelixArm>true</SkipHelixArm>
<!-- Disable gradle daemon on CI since the CI seems to try to wait for the daemon to shut down, which it doesn't do :) -->
<GradleOptions Condition="'$(ContinuousIntegrationBuild)' == 'true'">$(GradleOptions) -Dorg.gradle.daemon=false</GradleOptions>
<PublishDir>$(OutputPath)</PublishDir>