Browse Source

Adding WebTransport Handshake to Kestrel (#41877)

Daniel Genkin 3 years ago
parent
commit
d96a100bdd

+ 3 - 0
src/Http/Headers/src/HeaderNames.cs

@@ -189,6 +189,9 @@ public static class HeaderNames
     /// <summary>Gets the <c>Pragma</c> HTTP header name.</summary>
     public static readonly string Pragma = "Pragma";
 
+    /// <summary>Gets the <c>Protocol</c> HTTP header name.</summary>
+    public static readonly string Protocol = ":protocol";
+
     /// <summary>Gets the <c>Proxy-Authenticate</c> HTTP header name.</summary>
     public static readonly string ProxyAuthenticate = "Proxy-Authenticate";
 

+ 1 - 0
src/Http/Headers/src/PublicAPI.Unshipped.txt

@@ -1 +1,2 @@
 #nullable enable
+static readonly Microsoft.Net.Http.Headers.HeaderNames.Protocol -> string!

+ 10 - 1
src/Servers/Kestrel/Core/src/CoreStrings.resx

@@ -668,4 +668,13 @@ For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?l
   <data name="Http3ControlStreamErrorInitializingOutbound" xml:space="preserve">
     <value>Error initializing outbound control stream.</value>
   </data>
-</root>
+  <data name="Http3DatagramStatusMismatch" xml:space="preserve">
+    <value>HTTP/3 datagrams negotiation mismatch. Currently client has it '{clientStatus}' and server has it '{serverStatus}'</value>
+  </data>
+  <data name="Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader" xml:space="preserve">
+    <value>Method must be CONNECT when using the :protocol pseudo-header.</value>
+  </data>
+  <data name="Http3MissingAuthorityOrPathPseudoHeaders" xml:space="preserve">
+    <value>The :authority and/or :path pseudo-headers are missing.</value>
+  </data>
+</root>

File diff suppressed because it is too large
+ 134 - 108
src/Servers/Kestrel/Core/src/Internal/Http/HttpHeaders.Generated.cs


+ 10 - 0
src/Servers/Kestrel/Core/src/Internal/Http3/Http3Connection.cs

@@ -56,6 +56,10 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro
 
         _serverSettings.HeaderTableSize = (uint)httpLimits.Http3.HeaderTableSize;
         _serverSettings.MaxRequestHeaderFieldSectionSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
+        _serverSettings.EnableWebTransport = Convert.ToUInt32(context.ServiceContext.ServerOptions.EnableWebTransportAndH3Datagrams);
+        // technically these are 2 different settings so they should have separate values but the Chromium implementation requires
+        // them to both be 1 to useWebTransport.
+        _serverSettings.H3Datagram = Convert.ToUInt32(context.ServiceContext.ServerOptions.EnableWebTransportAndH3Datagrams);
     }
 
     private void UpdateHighestOpenedRequestStreamId(long streamId)
@@ -656,6 +660,12 @@ internal sealed class Http3Connection : IHttp3StreamLifetimeHandler, IRequestPro
                 break;
             case Http3SettingType.QPackBlockedStreams:
                 break;
+            case Http3SettingType.EnableWebTransport:
+                _clientSettings.EnableWebTransport = (uint)value;
+                break;
+            case Http3SettingType.H3Datagram:
+                _clientSettings.H3Datagram = (uint)value;
+                break;
             default:
                 throw new InvalidOperationException("Unexpected setting: " + type);
         }

+ 2 - 0
src/Servers/Kestrel/Core/src/Internal/Http3/Http3ControlStream.cs

@@ -331,6 +331,8 @@ internal abstract class Http3ControlStream : IHttp3Stream, IThreadPoolWorkItem
             case (long)Http3SettingType.QPackMaxTableCapacity:
             case (long)Http3SettingType.MaxFieldSectionSize:
             case (long)Http3SettingType.QPackBlockedStreams:
+            case (long)Http3SettingType.EnableWebTransport:
+            case (long)Http3SettingType.H3Datagram:
                 _context.StreamLifetimeHandler.OnInboundControlStreamSetting((Http3SettingType)id, value);
                 break;
             default:

+ 14 - 0
src/Servers/Kestrel/Core/src/Internal/Http3/Http3PeerSettings.cs

@@ -8,9 +8,13 @@ internal sealed class Http3PeerSettings
     // Note these are protocol defaults, not Kestrel defaults.
     public const uint DefaultHeaderTableSize = 0;
     public const uint DefaultMaxRequestHeaderFieldSize = uint.MaxValue;
+    public const uint DefaultEnableWebTransport = 0;
+    public const uint DefaultH3Datagram = 0;
 
     public uint HeaderTableSize { get; internal set; } = DefaultHeaderTableSize;
     public uint MaxRequestHeaderFieldSectionSize { get; internal set; } = DefaultMaxRequestHeaderFieldSize;
+    public uint EnableWebTransport { get; internal set; } = DefaultEnableWebTransport;
+    public uint H3Datagram { get; internal set; } = DefaultH3Datagram;
 
     // Gets the settings that are different from the protocol defaults (as opposed to the server defaults).
     internal List<Http3PeerSetting> GetNonProtocolDefaults()
@@ -29,6 +33,16 @@ internal sealed class Http3PeerSettings
             list.Add(new Http3PeerSetting(Http3SettingType.MaxFieldSectionSize, MaxRequestHeaderFieldSectionSize));
         }
 
+        if (EnableWebTransport != DefaultEnableWebTransport)
+        {
+            list.Add(new Http3PeerSetting(Http3SettingType.EnableWebTransport, EnableWebTransport));
+        }
+
+        if (H3Datagram != DefaultH3Datagram)
+        {
+            list.Add(new Http3PeerSetting(Http3SettingType.H3Datagram, H3Datagram));
+        }
+
         return list;
     }
 }

+ 14 - 1
src/Servers/Kestrel/Core/src/Internal/Http3/Http3SettingType.cs

@@ -13,5 +13,18 @@ internal enum Http3SettingType : long
     /// </summary>
     MaxFieldSectionSize = 0x6,
     // https://quicwg.org/base-drafts/draft-ietf-quic-qpack.html#section-5
-    QPackBlockedStreams = 0x7
+    QPackBlockedStreams = 0x7,
+
+    /// <summary>
+    /// SETTINGS_ENABLE_WEBTRANSPORT, default is 0 (off)
+    /// https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-01.html#name-http-3-settings-parameter-r
+    /// </summary>
+    EnableWebTransport = 0x2b603742,
+
+    /// <summary>
+    /// H3_DATAGRAM, default is 0 (off)
+    /// indicates that the server suppprts sending individual datagrams over Http/3
+    /// rather than just streams.
+    /// </summary>
+    H3Datagram = 0xffd277
 }

+ 27 - 3
src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

@@ -29,6 +29,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
     private static ReadOnlySpan<byte> AuthorityBytes => ":authority"u8;
     private static ReadOnlySpan<byte> MethodBytes => ":method"u8;
     private static ReadOnlySpan<byte> PathBytes => ":path"u8;
+    private static ReadOnlySpan<byte> ProtocolBytes => ":protocol"u8;
     private static ReadOnlySpan<byte> SchemeBytes => ":scheme"u8;
     private static ReadOnlySpan<byte> StatusBytes => ":status"u8;
     private static ReadOnlySpan<byte> ConnectionBytes => "connection"u8;
@@ -507,6 +508,10 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
         {
             return PseudoHeaderFields.Authority;
         }
+        else if (name.SequenceEqual(ProtocolBytes))
+        {
+            return PseudoHeaderFields.Protocol;
+        }
         else
         {
             return PseudoHeaderFields.Unknown;
@@ -821,7 +826,25 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
             await OnEndStreamReceived();
         }
 
-        if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
+        // https://datatracker.ietf.org/doc/html/draft-ietf-webtrans-http3/#section-3.3
+        if (_context.ServiceContext.ServerOptions.EnableWebTransportAndH3Datagrams && HttpRequestHeaders.HeaderProtocol.Count > 0)
+        {
+            if (!_isMethodConnect)
+            {
+                throw new Http3StreamErrorException(CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader, Http3ErrorCode.ProtocolError);
+            }
+
+            if (!_parsedPseudoHeaderFields.HasFlag(PseudoHeaderFields.Authority) || !_parsedPseudoHeaderFields.HasFlag(PseudoHeaderFields.Path))
+            {
+                throw new Http3StreamErrorException(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders, Http3ErrorCode.ProtocolError);
+            }
+
+            if (_context.ClientPeerSettings.H3Datagram != _context.ServerPeerSettings.H3Datagram)
+            {
+                throw new Http3StreamErrorException(CoreStrings.FormatHttp3DatagramStatusMismatch(_context.ClientPeerSettings.H3Datagram == 1, _context.ServerPeerSettings.H3Datagram == 1), Http3ErrorCode.SettingsError);
+            }
+        }
+        else if (!_isMethodConnect && (_parsedPseudoHeaderFields & _mandatoryRequestPseudoHeaderFields) != _mandatoryRequestPseudoHeaderFields)
         {
             // All HTTP/3 requests MUST include exactly one valid value for the :method, :scheme, and :path pseudo-header
             // fields, unless it is a CONNECT request. An HTTP request that omits mandatory pseudo-header
@@ -928,8 +951,8 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
             return false;
         }
 
-        // CONNECT - :scheme and :path must be excluded
-        if (Method == Http.HttpMethod.Connect)
+        // CONNECT - :scheme and :path must be excluded=
+        if (Method == Http.HttpMethod.Connect && HttpRequestHeaders.HeaderProtocol.Count == 0)
         {
             if (!string.IsNullOrEmpty(RequestHeaders[HeaderNames.Scheme]) || !string.IsNullOrEmpty(RequestHeaders[HeaderNames.Path]))
             {
@@ -1157,6 +1180,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
         Path = 0x4,
         Scheme = 0x8,
         Status = 0x10,
+        Protocol = 0x20,
         Unknown = 0x40000000
     }
 

+ 18 - 0
src/Servers/Kestrel/Core/src/KestrelServerOptions.cs

@@ -157,6 +157,24 @@ public class KestrelServerOptions
     /// </summary>
     internal bool IsDevCertLoaded { get; set; }
 
+    /// <summary>
+    /// Internal AppContext switch to toggle the WebTransport and HTTP/3 datagrams experiemental features.
+    /// </summary>
+    private bool? _enableWebTransportAndH3Datagrams;
+    internal bool EnableWebTransportAndH3Datagrams
+    {
+        get
+        {
+            if (!_enableWebTransportAndH3Datagrams.HasValue)
+            {
+                _enableWebTransportAndH3Datagrams = AppContext.TryGetSwitch("Microsoft.AspNetCore.Server.Kestrel.Experimental.WebTransportAndH3Datagrams", out var enabled) && enabled;
+            }
+
+            return _enableWebTransportAndH3Datagrams.Value;
+        }
+        set => _enableWebTransportAndH3Datagrams = value;
+    }
+
     /// <summary>
     /// Specifies a configuration Action to run for each newly created endpoint. Calling this again will replace
     /// the prior action.

+ 5 - 0
src/Servers/Kestrel/samples/Http3SampleApp/Http3SampleApp.csproj

@@ -16,4 +16,9 @@
     <Reference Include="Microsoft.Extensions.Hosting" />
     <Reference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" />
   </ItemGroup>
+
+  <ItemGroup>
+    <!-- Turn on the WebTransport AppContext switch -->
+    <RuntimeHostConfigurationOption Include="Microsoft.AspNetCore.Server.Kestrel.Experimental.WebTransportAndH3Datagrams" Value="true" />
+  </ItemGroup>
 </Project>

+ 64 - 11
src/Servers/Kestrel/samples/Http3SampleApp/Program.cs

@@ -1,7 +1,9 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Net;
 using System.Net.Security;
+using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
 using Microsoft.AspNetCore.Connections;
 using Microsoft.AspNetCore.Internal;
@@ -22,25 +24,18 @@ public class Program
             })
             .ConfigureWebHost(webHost =>
             {
+                var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false);
+
                 webHost.UseKestrel()
                 .ConfigureKestrel((context, options) =>
                 {
-                    var cert = CertificateLoader.LoadFromStoreCert("localhost", StoreName.My.ToString(), StoreLocation.CurrentUser, false);
-
-                    options.ConfigureHttpsDefaults(httpsOptions =>
-                    {
-                        httpsOptions.ServerCertificate = cert;
-                        // httpsOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
-                        // httpsOptions.AllowAnyClientCertificate();
-                    });
-
                     options.ListenAnyIP(5000, listenOptions =>
                     {
                         listenOptions.UseConnectionLogging();
                         listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
                     });
 
-                    options.ListenAnyIP(5001, listenOptions =>
+                    options.Listen(IPAddress.Any, 5001, listenOptions =>
                     {
                         listenOptions.UseHttps();
                         listenOptions.UseConnectionLogging();
@@ -49,8 +44,8 @@ public class Program
 
                     options.ListenAnyIP(5002, listenOptions =>
                     {
-                        listenOptions.UseHttps(StoreName.My, "localhost");
                         listenOptions.UseConnectionLogging();
+                        listenOptions.UseHttps(StoreName.My, "localhost");
                         listenOptions.Protocols = HttpProtocols.Http3;
                     });
 
@@ -108,6 +103,14 @@ public class Program
                         listenOptions.UseConnectionLogging();
                         listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
                     });
+
+                    // Port configured for WebTransport
+                    options.Listen(IPAddress.Any, 5007, listenOptions =>
+                    {
+                        listenOptions.UseHttps(GenerateManualCertificate());
+                        listenOptions.UseConnectionLogging();
+                        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
+                    });
                 })
                 .UseStartup<Startup>();
             });
@@ -119,4 +122,54 @@ public class Program
 
         host.Run();
     }
+
+    // Adapted from: https://github.com/wegylexy/webtransport
+    // We will need to eventually merge this with existing Kestrel certificate generation
+    // tracked in issue #41762
+    private static X509Certificate2 GenerateManualCertificate()
+    {
+        X509Certificate2 cert = null;
+        var store = new X509Store("KestrelWebTransportCertificates", StoreLocation.CurrentUser);
+        store.Open(OpenFlags.ReadWrite);
+        if (store.Certificates.Count > 0)
+        {
+            cert = store.Certificates[^1];
+
+            // rotate key after it expires
+            if (DateTime.Parse(cert.GetExpirationDateString(), null) < DateTimeOffset.UtcNow)
+            {
+                cert = null;
+            }
+        }
+        if (cert == null)
+        {
+            // generate a new cert
+            var now = DateTimeOffset.UtcNow;
+            SubjectAlternativeNameBuilder sanBuilder = new();
+            sanBuilder.AddDnsName("localhost");
+            using var ec = ECDsa.Create(ECCurve.NamedCurves.nistP256);
+            CertificateRequest req = new("CN=localhost", ec, HashAlgorithmName.SHA256);
+            // Adds purpose
+            req.CertificateExtensions.Add(new X509EnhancedKeyUsageExtension(new OidCollection
+            {
+                new("1.3.6.1.5.5.7.3.1") // serverAuth
+            }, false));
+            // Adds usage
+            req.CertificateExtensions.Add(new X509KeyUsageExtension(X509KeyUsageFlags.DigitalSignature, false));
+            // Adds subject alternate names
+            req.CertificateExtensions.Add(sanBuilder.Build());
+            // Sign
+            using var crt = req.CreateSelfSigned(now, now.AddDays(14)); // 14 days is the max duration of a certificate for this
+            cert = new(crt.Export(X509ContentType.Pfx));
+
+            // Save
+            store.Add(cert);
+        }
+        store.Close();
+
+        var hash = SHA256.HashData(cert.RawData);
+        var certStr = Convert.ToBase64String(hash);
+        Console.WriteLine($"\n\n\n\n\nCertificate: {certStr}\n\n\n\n"); // <-- you will need to put this output into the JS API call to allo wthe connection
+        return cert;
+    }
 }

+ 4 - 1
src/Servers/Kestrel/shared/KnownHeaders.cs

@@ -27,6 +27,7 @@ public class KnownHeaders
         HeaderNames.Connection,
         HeaderNames.Scheme,
         HeaderNames.Path,
+        HeaderNames.Protocol,
         HeaderNames.Method,
         HeaderNames.Authority,
         HeaderNames.Host,
@@ -45,7 +46,8 @@ public class KnownHeaders
         "Method", // :method
         "Path", // :path
         "Scheme", // :scheme
-        "Status" // :status
+        "Status", // :status
+        "Protocol" // :protocol
     };
 
     public static readonly string[] NonApiHeaders =
@@ -132,6 +134,7 @@ public class KnownHeaders
             HeaderNames.IfRange,
             HeaderNames.IfUnmodifiedSince,
             HeaderNames.MaxForwards,
+            HeaderNames.Protocol,
             HeaderNames.ProxyAuthorization,
             HeaderNames.Referer,
             HeaderNames.Range,

+ 138 - 0
src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/WebTransportTests.cs

@@ -0,0 +1,138 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Net;
+using System.Net.Http;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Net.Http.Headers;
+using Http3SettingType = Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http3.Http3SettingType;
+
+namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
+
+public class WebTransportTests : Http3TestBase
+{
+    [Fact]
+    public async Task WebTransportHandshake_ClientToServerPasses()
+    {
+        _serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = true;
+
+        await Http3Api.InitializeConnectionAsync(_noopApplication);
+        var controlStream = await Http3Api.CreateControlStream();
+        var controlStream2 = await Http3Api.GetInboundControlStream();
+
+        var settings = new Http3PeerSettings()
+        {
+            EnableWebTransport = 1,
+            H3Datagram = 1,
+        };
+
+        await controlStream.SendSettingsAsync(settings.GetNonProtocolDefaults());
+        var response1 = await controlStream2.ExpectSettingsAsync();
+
+        await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout();
+
+        Assert.Equal(1, response1[(long)Http3SettingType.EnableWebTransport]);
+
+        var requestStream = await Http3Api.CreateRequestStream();
+        var headersConnectFrame = new[]
+        {
+            new KeyValuePair<string, string>(HeaderNames.Method, "CONNECT"),
+            new KeyValuePair<string, string>(HeaderNames.Protocol, "webtransport"),
+            new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
+            new KeyValuePair<string, string>(HeaderNames.Path, "/"),
+            new KeyValuePair<string, string>(HeaderNames.Authority, "server.example.com"),
+            new KeyValuePair<string, string>(HeaderNames.Origin, "server.example.com")
+        };
+
+        await requestStream.SendHeadersAsync(headersConnectFrame);
+        var response2 = await requestStream.ExpectHeadersAsync();
+
+        Assert.Equal((int)HttpStatusCode.OK, Convert.ToInt32(response2[HeaderNames.Status], null));
+
+        await requestStream.OnDisposedTask.DefaultTimeout();
+    }
+
+    [Theory]
+    [InlineData(
+        ((long)Http3ErrorCode.ProtocolError),
+        nameof(CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader),
+        nameof(HeaderNames.Method), "GET", // incorrect method (verifies that webtransport doesn't break regular Http/3 get)
+        nameof(HeaderNames.Protocol), "webtransport",
+        nameof(HeaderNames.Scheme), "http",
+        nameof(HeaderNames.Path), "/",
+        nameof(HeaderNames.Authority), "server.example.com",
+        nameof(HeaderNames.Origin), "server.example.com")]
+    [InlineData(
+        ((long)Http3ErrorCode.ProtocolError),
+        nameof(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders),
+        nameof(HeaderNames.Method), "CONNECT",
+        nameof(HeaderNames.Protocol), "webtransport",
+        nameof(HeaderNames.Scheme), "http",
+        nameof(HeaderNames.Authority), "server.example.com",
+        nameof(HeaderNames.Origin), "server.example.com")]  // no path
+    [InlineData(
+        ((long)Http3ErrorCode.ProtocolError),
+        nameof(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders),
+        nameof(HeaderNames.Method), "CONNECT",
+        nameof(HeaderNames.Protocol), "webtransport",
+        nameof(HeaderNames.Scheme), "http",
+        nameof(HeaderNames.Path), "/",
+        nameof(HeaderNames.Origin), "server.example.com")]  // no authority
+    public async Task WebTransportHandshake_IncorrectHeadersRejects(long error, string targetErrorMessage, params string[] headers) // todo replace the "" with CoreStrings.... then push (maybe also update the waitforstreamerror function) and resolve stephen's comment
+    {
+        _serviceContext.ServerOptions.EnableWebTransportAndH3Datagrams = true;
+
+        await Http3Api.InitializeConnectionAsync(_noopApplication);
+        var controlStream = await Http3Api.CreateControlStream();
+        var controlStream2 = await Http3Api.GetInboundControlStream();
+
+        var settings = new Http3PeerSettings()
+        {
+            EnableWebTransport = 1,
+            H3Datagram = 1,
+        };
+
+        await controlStream.SendSettingsAsync(settings.GetNonProtocolDefaults());
+        var response1 = await controlStream2.ExpectSettingsAsync();
+
+        await Http3Api.ServerReceivedSettingsReader.ReadAsync().DefaultTimeout();
+
+        Assert.Equal(1, response1[(long)Http3SettingType.EnableWebTransport]);
+
+        var requestStream = await Http3Api.CreateRequestStream();
+
+        var headersConnectFrame = new List<KeyValuePair<string, string>>();
+        for (var i = 0; i < headers.Length; i += 2)
+        {
+            headersConnectFrame.Add(new KeyValuePair<string, string>(GetHeaderFromName(headers[i]), headers[i + 1]));
+        }
+        await requestStream.SendHeadersAsync(headersConnectFrame);
+
+        await requestStream.WaitForStreamErrorAsync((Http3ErrorCode)error, AssertExpectedErrorMessages, GetCoreStringFromName(targetErrorMessage));
+    }
+
+    private static string GetCoreStringFromName(string headerName)
+    {
+        return headerName switch
+        {
+            nameof(CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders) => CoreStrings.Http3MissingAuthorityOrPathPseudoHeaders,
+            nameof(CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader) => CoreStrings.Http3MethodMustBeConnectWhenUsingProtocolPseudoHeader,
+            _ => throw new Exception("Core string not mapped yet")
+        };
+    }
+
+    private static string GetHeaderFromName(string coreStringName)
+    {
+        return coreStringName switch
+        {
+            nameof(HeaderNames.Method) => HeaderNames.Method,
+            nameof(HeaderNames.Protocol) => HeaderNames.Protocol,
+            nameof(HeaderNames.Scheme) => HeaderNames.Scheme,
+            nameof(HeaderNames.Path) => HeaderNames.Path,
+            nameof(HeaderNames.Authority) => HeaderNames.Authority,
+            nameof(HeaderNames.Origin) => HeaderNames.Origin,
+            _ => throw new Exception("Header name not mapped yet")
+        };
+    }
+}

+ 14 - 1
src/Shared/runtime/Http3/Http3SettingType.cs

@@ -33,6 +33,19 @@ namespace System.Net.Http
         /// The maximum number of request streams that can be blocked waiting for QPack instructions. The default is 0.
         /// https://tools.ietf.org/html/draft-ietf-quic-qpack-11#section-5
         /// </summary>
-        QPackBlockedStreams = 0x7
+        QPackBlockedStreams = 0x7,
+
+        /// <summary>
+        /// SETTINGS_ENABLE_WEBTRANSPORT, default is 0 (off)
+        /// https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-01.html#name-http-3-settings-parameter-r
+        /// </summary>
+        EnableWebTransport = 0x2b603742,
+
+        /// <summary>
+        /// H3_DATAGRAM, default is 0 (off)
+        /// indicates that the server suppprts sending individual datagrams over Http/3
+        /// rather than just streams.
+        /// </summary>
+        H3Datagram = 0xffd277
     }
 }

Some files were not shown because too many files changed in this diff