Merge branch 'master' into johluo/migrating-extensions
This commit is contained in:
commit
793857f778
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace System.Net.Http.HPack
|
|||
{
|
||||
internal class HPackDecoder
|
||||
{
|
||||
private enum State
|
||||
private enum State : byte
|
||||
{
|
||||
Ready,
|
||||
HeaderFieldIndex,
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Reference in New Issue