Add test support for multiple connections in Http2 (dotnet/corefx#38561)
authorKrzysztof Wicher <kwicher@microsoft.com>
Wed, 19 Jun 2019 23:34:29 +0000 (16:34 -0700)
committerGitHub <noreply@github.com>
Wed, 19 Jun 2019 23:34:29 +0000 (16:34 -0700)
* Add test support for multiple connections in Http2

* manual merge conflict fix, fix recently broken test (also in dotnet/corefx#38694)

Commit migrated from https://github.com/dotnet/corefx/commit/290a6e54a482ee950c4b812ad85f8de2d795932f

src/libraries/Common/tests/System/Net/Http/Http2Frames.cs
src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs [new file with mode: 0644]
src/libraries/Common/tests/System/Net/Http/Http2LoopbackServer.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HPackTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.Http2.cs
src/libraries/System.Net.Http/tests/FunctionalTests/SocketsHttpHandlerTest.cs
src/libraries/System.Net.Http/tests/FunctionalTests/System.Net.Http.Functional.Tests.csproj

index 03f7f2d..83971cb 100644 (file)
@@ -348,14 +348,14 @@ namespace System.Net.Test.Common
         public byte[] Data;
 
         public PingFrame(byte[] data, FrameFlags flags, int streamId) :
-            base(Frame.FrameHeaderLength + 8, FrameType.Ping, flags, streamId)
+            base(8, FrameType.Ping, flags, streamId)
         {
             Data = data;
         }
 
         public static PingFrame ReadFrom(Frame header, ReadOnlySpan<byte> buffer)
         {
-            byte[] data = buffer.Slice(Frame.FrameHeaderLength).ToArray();
+            byte[] data = buffer.ToArray();
 
             return new PingFrame(data, header.Flags, header.StreamId);
         }
@@ -363,8 +363,9 @@ namespace System.Net.Test.Common
         public override void WriteTo(Span<byte> buffer)
         {
             base.WriteTo(buffer);
+            buffer = buffer.Slice(Frame.FrameHeaderLength, 8);
 
-            Data.CopyTo(buffer.Slice(Frame.FrameHeaderLength));
+            Data.CopyTo(buffer);
         }
 
         public override string ToString()
diff --git a/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs b/src/libraries/Common/tests/System/Net/Http/Http2LoopbackConnection.cs
new file mode 100644 (file)
index 0000000..f95179d
--- /dev/null
@@ -0,0 +1,615 @@
+// 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.Collections.Generic;
+using System.IO;
+using System.Net.Security;
+using System.Net.Sockets;
+using System.Security.Authentication;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Xunit;
+
+namespace System.Net.Test.Common
+{
+    public class Http2LoopbackConnection
+    {
+        private Socket _connectionSocket;
+        private Stream _connectionStream;
+        private bool _ignoreSettingsAck;
+        private bool _ignoreWindowUpdates;
+        public static TimeSpan Timeout => Http2LoopbackServer.Timeout;
+
+        private readonly byte[] _prefix;
+        public string PrefixString => Encoding.UTF8.GetString(_prefix, 0, _prefix.Length);
+        public bool IsInvalid => _connectionSocket == null;
+
+        public Http2LoopbackConnection(Socket socket, Http2Options httpOptions)
+        {
+            _connectionSocket = socket;
+            _connectionStream = new NetworkStream(_connectionSocket, true);
+
+            if (httpOptions.UseSsl)
+            {
+                var sslStream = new SslStream(_connectionStream, false, delegate { return true; });
+
+                using (var cert = Configuration.Certificates.GetServerCertificate())
+                {
+                    SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();
+
+                    options.EnabledSslProtocols = httpOptions.SslProtocols;
+
+                    var protocols = new List<SslApplicationProtocol>();
+                    protocols.Add(SslApplicationProtocol.Http2);
+                    protocols.Add(SslApplicationProtocol.Http11);
+                    options.ApplicationProtocols = protocols;
+
+                    options.ServerCertificate = cert;
+
+                    options.ClientCertificateRequired = false;
+
+                    sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).Wait();
+                }
+
+                _connectionStream = sslStream;
+            }
+
+            _prefix = new byte[24];
+            if (!FillBufferAsync(_prefix).Result)
+            {
+                throw new Exception("Connection stream closed while attempting to read connection preface.");
+            }
+        }
+
+        public async Task SendConnectionPrefaceAsync()
+        {
+            // Send the initial server settings frame.
+            Frame emptySettings = new Frame(0, FrameType.Settings, FrameFlags.None, 0);
+            await WriteFrameAsync(emptySettings).ConfigureAwait(false);
+
+            // Receive and ACK the client settings frame.
+            Frame clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+            clientSettings.Flags = clientSettings.Flags | FrameFlags.Ack;
+            await WriteFrameAsync(clientSettings).ConfigureAwait(false);
+
+            // Receive the client ACK of the server settings frame.
+            clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+        }
+
+        public async Task WriteFrameAsync(Frame frame)
+        {
+            byte[] writeBuffer = new byte[Frame.FrameHeaderLength + frame.Length];
+            frame.WriteTo(writeBuffer);
+            await _connectionStream.WriteAsync(writeBuffer, 0, writeBuffer.Length).ConfigureAwait(false);
+        }
+
+        // Read until the buffer is full
+        // Return false on EOF, throw on partial read
+        private async Task<bool> FillBufferAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
+        {
+            int readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+            if (readBytes == 0)
+            {
+                return false;
+            }
+
+            buffer = buffer.Slice(readBytes);
+            while (buffer.Length > 0)
+            {
+                readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+                if (readBytes == 0)
+                {
+                    throw new Exception("Connection closed when expecting more data.");
+                }
+
+                buffer = buffer.Slice(readBytes);
+            }
+
+            return true;
+        }
+
+        public async Task<Frame> ReadFrameAsync(TimeSpan timeout)
+        {
+            using CancellationTokenSource timeoutCts = new CancellationTokenSource(timeout);
+            return await ReadFrameAsync(timeoutCts.Token);
+        }
+
+        private async Task<Frame> ReadFrameAsync(CancellationToken cancellationToken)
+        {
+            // First read the frame headers, which should tell us how long the rest of the frame is.
+            byte[] headerBytes = new byte[Frame.FrameHeaderLength];
+
+            try
+            {
+                if (!await FillBufferAsync(headerBytes, cancellationToken).ConfigureAwait(false))
+                {
+                    return null;
+                }
+            }
+            catch (IOException)
+            {
+                // eat errors when client aborts connection and return null.
+                return null;
+            }
+
+            Frame header = Frame.ReadFrom(headerBytes);
+
+            // Read the data segment of the frame, if it is present.
+            byte[] data = new byte[header.Length];
+            if (header.Length > 0 && !await FillBufferAsync(data, cancellationToken).ConfigureAwait(false))
+            {
+                throw new Exception("Connection stream closed while attempting to read frame body.");
+            }
+
+            if (_ignoreSettingsAck && header.Type == FrameType.Settings && header.Flags == FrameFlags.Ack)
+            {
+                _ignoreSettingsAck = false;
+                return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
+            }
+
+            if (_ignoreWindowUpdates && header.Type == FrameType.WindowUpdate)
+            {
+                return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
+            }
+
+            // Construct the correct frame type and return it.
+            switch (header.Type)
+            {
+                case FrameType.Settings:
+                    return SettingsFrame.ReadFrom(header, data);
+                case FrameType.Data:
+                    return DataFrame.ReadFrom(header, data);
+                case FrameType.Headers:
+                    return HeadersFrame.ReadFrom(header, data);
+                case FrameType.Priority:
+                    return PriorityFrame.ReadFrom(header, data);
+                case FrameType.RstStream:
+                    return RstStreamFrame.ReadFrom(header, data);
+                case FrameType.Ping:
+                    return PingFrame.ReadFrom(header, data);
+                case FrameType.GoAway:
+                    return GoAwayFrame.ReadFrom(header, data);
+                default:
+                    return header;
+            }
+        }
+
+        // Reset and return underlying networking objects.
+        public (Socket, Stream) ResetNetwork()
+        {
+            Socket oldSocket = _connectionSocket;
+            Stream oldStream = _connectionStream;
+            _connectionSocket = null;
+            _connectionStream = null;
+            _ignoreSettingsAck = false;
+
+            return (oldSocket, oldStream);
+        }
+
+        public void ExpectSettingsAck()
+        {
+            // The timing of when we receive the settings ack is not guaranteed.
+            // To simplify frame processing, just record that we are expecting one,
+            // and then filter it out in ReadFrameAsync above.
+
+            Assert.False(_ignoreSettingsAck);
+            _ignoreSettingsAck = true;
+        }
+
+        public void IgnoreWindowUpdates()
+        {
+            _ignoreWindowUpdates = true;
+        }
+
+        public void ShutdownSend()
+        {
+            _connectionSocket.Shutdown(SocketShutdown.Send);
+        }
+
+        // This will wait for the client to close the connection,
+        // and ignore any meaningless frames -- i.e. WINDOW_UPDATE or expected SETTINGS ACK --
+        // that we see while waiting for the client to close.
+        // Only call this after sending a GOAWAY.
+        public async Task WaitForConnectionShutdownAsync()
+        {
+            // Shutdown our send side, so the client knows there won't be any more frames coming.
+            ShutdownSend();
+
+            IgnoreWindowUpdates();
+            Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+            if (frame != null)
+            {
+                throw new Exception($"Unexpected frame received while waiting for client shutdown: {frame}");
+            }
+
+            _connectionStream.Close();
+
+            _connectionSocket = null;
+            _connectionStream = null;
+
+            _ignoreSettingsAck = false;
+            _ignoreWindowUpdates = false;
+        }
+
+        public async Task<int> ReadRequestHeaderAsync()
+        {
+            // Receive HEADERS frame for request.
+            Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+            if (frame == null)
+            {
+                throw new IOException("Failed to read Headers frame.");
+            }
+            Assert.Equal(FrameType.Headers, frame.Type);
+            Assert.Equal(FrameFlags.EndHeaders | FrameFlags.EndStream, frame.Flags);
+            return frame.StreamId;
+        }
+
+        private static (int bytesConsumed, int value) DecodeInteger(ReadOnlySpan<byte> headerBlock, byte prefixMask)
+        {
+            int value = headerBlock[0] & prefixMask;
+            if (value != prefixMask)
+            {
+                return (1, value);
+            }
+
+            byte b = headerBlock[1];
+            if ((b & 0b10000000) != 0)
+            {
+                throw new Exception("long integers currently not supported");
+            }
+            return (2, prefixMask + b);
+        }
+
+        private static int EncodeInteger(int value, byte prefix, byte prefixMask, Span<byte> headerBlock)
+        {
+            byte prefixLimit = (byte)(~prefixMask);
+
+            if (value < prefixLimit)
+            {
+                headerBlock[0] = (byte)(prefix | value);
+                return 1;
+            }
+
+            headerBlock[0] = (byte)(prefix | prefixLimit);
+            int bytesGenerated = 1;
+
+            value -= prefixLimit;
+
+            while (value >= 0x80)
+            {
+                headerBlock[bytesGenerated] = (byte)((value & 0x7F) | 0x80);
+                value = value >> 7;
+                bytesGenerated++;
+            }
+
+            headerBlock[bytesGenerated] = (byte)value;
+            bytesGenerated++;
+
+            return bytesGenerated;
+        }
+
+        private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan<byte> headerBlock)
+        {
+            (int bytesConsumed, int stringLength) = DecodeInteger(headerBlock, 0b01111111);
+            if ((headerBlock[0] & 0b10000000) != 0)
+            {
+                // Huffman encoded
+                byte[] buffer = new byte[stringLength * 2];
+                int bytesDecoded = HuffmanDecoder.Decode(headerBlock.Slice(bytesConsumed, stringLength), buffer);
+                string value = Encoding.ASCII.GetString(buffer, 0, bytesDecoded);
+                return (bytesConsumed + stringLength, value);
+            }
+            else
+            {
+                string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength));
+                return (bytesConsumed + stringLength, value);
+            }
+        }
+
+        private static int EncodeString(string value, Span<byte> headerBlock)
+        {
+            int bytesGenerated = EncodeInteger(value.Length, 0, 0x80, headerBlock);
+
+            bytesGenerated += Encoding.ASCII.GetBytes(value.AsSpan(), headerBlock.Slice(bytesGenerated));
+
+            return bytesGenerated;
+        }
+
+        private static readonly HttpHeaderData[] s_staticTable = new HttpHeaderData[]
+        {
+            new HttpHeaderData(":authority", ""),
+            new HttpHeaderData(":method", "GET"),
+            new HttpHeaderData(":method", "POST"),
+            new HttpHeaderData(":path", "/"),
+            new HttpHeaderData(":path", "/index.html"),
+            new HttpHeaderData(":scheme", "http"),
+            new HttpHeaderData(":scheme", "https"),
+            new HttpHeaderData(":status", "200"),
+            new HttpHeaderData(":status", "204"),
+            new HttpHeaderData(":status", "206"),
+            new HttpHeaderData(":status", "304"),
+            new HttpHeaderData(":status", "400"),
+            new HttpHeaderData(":status", "404"),
+            new HttpHeaderData(":status", "500"),
+            new HttpHeaderData("accept-charset", ""),
+            new HttpHeaderData("accept-encoding", "gzip, deflate"),
+            new HttpHeaderData("accept-language", ""),
+            new HttpHeaderData("accept-ranges", ""),
+            new HttpHeaderData("accept", ""),
+            new HttpHeaderData("access-control-allow-origin", ""),
+            new HttpHeaderData("age", ""),
+            new HttpHeaderData("allow", ""),
+            new HttpHeaderData("authorization", ""),
+            new HttpHeaderData("cache-control", ""),
+            new HttpHeaderData("content-disposition", ""),
+            new HttpHeaderData("content-encoding", ""),
+            new HttpHeaderData("content-language", ""),
+            new HttpHeaderData("content-length", ""),
+            new HttpHeaderData("content-location", ""),
+            new HttpHeaderData("content-range", ""),
+            new HttpHeaderData("content-type", ""),
+            new HttpHeaderData("cookie", ""),
+            new HttpHeaderData("date", ""),
+            new HttpHeaderData("etag", ""),
+            new HttpHeaderData("expect", ""),
+            new HttpHeaderData("expires", ""),
+            new HttpHeaderData("from", ""),
+            new HttpHeaderData("host", ""),
+            new HttpHeaderData("if-match", ""),
+            new HttpHeaderData("if-modified-since", ""),
+            new HttpHeaderData("if-none-match", ""),
+            new HttpHeaderData("if-range", ""),
+            new HttpHeaderData("if-unmodified-since", ""),
+            new HttpHeaderData("last-modified", ""),
+            new HttpHeaderData("link", ""),
+            new HttpHeaderData("location", ""),
+            new HttpHeaderData("max-forwards", ""),
+            new HttpHeaderData("proxy-authenticate", ""),
+            new HttpHeaderData("proxy-authorization", ""),
+            new HttpHeaderData("range", ""),
+            new HttpHeaderData("referer", ""),
+            new HttpHeaderData("refresh", ""),
+            new HttpHeaderData("retry-after", ""),
+            new HttpHeaderData("server", ""),
+            new HttpHeaderData("set-cookie", ""),
+            new HttpHeaderData("strict-transport-security", ""),
+            new HttpHeaderData("transfer-encoding", ""),
+            new HttpHeaderData("user-agent", ""),
+            new HttpHeaderData("vary", ""),
+            new HttpHeaderData("via", ""),
+            new HttpHeaderData("www-authenticate", "")
+        };
+
+        private static HttpHeaderData GetHeaderForIndex(int index)
+        {
+            return s_staticTable[index - 1];
+        }
+
+        private static (int bytesConsumed, HttpHeaderData headerData) DecodeLiteralHeader(ReadOnlySpan<byte> headerBlock, byte prefixMask)
+        {
+            int i = 0;
+
+            (int bytesConsumed, int index) = DecodeInteger(headerBlock, prefixMask);
+            i += bytesConsumed;
+
+            string name;
+            if (index == 0)
+            {
+                (bytesConsumed, name) = DecodeString(headerBlock.Slice(i));
+                i += bytesConsumed;
+            }
+            else
+            {
+                name = GetHeaderForIndex(index).Name;
+            }
+
+            string value;
+            (bytesConsumed, value) = DecodeString(headerBlock.Slice(i));
+            i += bytesConsumed;
+
+            return (i, new HttpHeaderData(name, value));
+        }
+
+        private static (int bytesConsumed, HttpHeaderData headerData) DecodeHeader(ReadOnlySpan<byte> headerBlock)
+        {
+            int i = 0;
+
+            byte b = headerBlock[0];
+            if ((b & 0b10000000) != 0)
+            {
+                // Indexed header
+                (int bytesConsumed, int index) = DecodeInteger(headerBlock, 0b01111111);
+                i += bytesConsumed;
+
+                return (i, GetHeaderForIndex(index));
+            }
+            else if ((b & 0b11000000) == 0b01000000)
+            {
+                // Literal with indexing
+                return DecodeLiteralHeader(headerBlock, 0b00111111);
+            }
+            else if ((b & 0b11100000) == 0b00100000)
+            {
+                // Table size update
+                throw new Exception("table size update not supported");
+            }
+            else
+            {
+                // Literal, never indexed
+                return DecodeLiteralHeader(headerBlock, 0b00001111);
+            }
+        }
+
+        private static int EncodeHeader(HttpHeaderData headerData, Span<byte> headerBlock)
+        {
+            // Always encode as literal, no indexing
+            int bytesGenerated = EncodeInteger(0, 0, 0b11110000, headerBlock);
+            bytesGenerated += EncodeString(headerData.Name, headerBlock.Slice(bytesGenerated));
+            bytesGenerated += EncodeString(headerData.Value, headerBlock.Slice(bytesGenerated));
+            return bytesGenerated;
+        }
+
+        public async Task<byte[]> ReadBodyAsync()
+        {
+            byte[] body = null;
+            Frame frame;
+
+            do
+            {
+                frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+                if (frame == null || frame.Type == FrameType.RstStream)
+                {
+                    throw new IOException( frame == null ? "End of stream" : "Got RST");
+                }
+
+                Assert.Equal(FrameType.Data, frame.Type);
+
+                if (frame.Length > 1)
+                {
+                    DataFrame dataFrame = (DataFrame)frame;
+
+                    if (body == null)
+                    {
+                        body = dataFrame.Data.ToArray();
+                    }
+                    else
+                    {
+                        byte[] newBuffer = new byte[body.Length + dataFrame.Data.Length];
+
+                        body.CopyTo(newBuffer, 0);
+                        dataFrame.Data.Span.CopyTo(newBuffer.AsSpan().Slice(body.Length));
+                        body= newBuffer;
+                    }
+                }
+            }
+            while ((frame.Flags & FrameFlags.EndStream) == 0);
+
+            return body;
+        }
+
+        public async Task<(int streamId, HttpRequestData requestData)> ReadAndParseRequestHeaderAsync(bool readBody = true)
+        {
+            HttpRequestData requestData = new HttpRequestData();
+
+            // Receive HEADERS frame for request.
+            Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+            if (frame == null)
+            {
+                throw new IOException("Failed to read Headers frame.");
+            }
+            Assert.Equal(FrameType.Headers, frame.Type);
+            HeadersFrame headersFrame = (HeadersFrame) frame;
+
+            // TODO CONTINUATION support
+            Assert.Equal(FrameFlags.EndHeaders, FrameFlags.EndHeaders & headersFrame.Flags);
+
+            int streamId = headersFrame.StreamId;
+
+            Memory<byte> data = headersFrame.Data;
+            int i = 0;
+            while (i < data.Length)
+            {
+                (int bytesConsumed, HttpHeaderData headerData) = DecodeHeader(data.Span.Slice(i));
+
+                byte[] headerRaw = data.Span.Slice(i, bytesConsumed).ToArray();
+                headerData = new HttpHeaderData(headerData.Name, headerData.Value, headerRaw);
+
+                requestData.Headers.Add(headerData);
+                i += bytesConsumed;
+            }
+
+            // Extract method and path
+            requestData.Method = requestData.GetSingleHeaderValue(":method");
+            requestData.Path = requestData.GetSingleHeaderValue(":path");
+
+            if (readBody && (frame.Flags & FrameFlags.EndStream) == 0)
+            {
+                // Read body until end of stream if needed.
+                requestData.Body = await ReadBodyAsync();
+            }
+
+            return (streamId, requestData);
+        }
+
+        public async Task SendGoAway(int lastStreamId)
+        {
+            GoAwayFrame frame = new GoAwayFrame(lastStreamId, 0, new byte[] { }, 0);
+            await WriteFrameAsync(frame).ConfigureAwait(false);
+        }
+
+        public async Task PingPong()
+        {
+            PingFrame ping = new PingFrame(new byte[8] { 1, 2, 3, 4, 50, 60, 70, 80 }, FrameFlags.None, 0);
+            await WriteFrameAsync(ping).ConfigureAwait(false);
+            await ReadFrameAsync(Timeout).ConfigureAwait(false);
+        }
+
+        public async Task SendDefaultResponseHeadersAsync(int streamId)
+        {
+            byte[] headers = new byte[] { 0x88 };   // Encoding for ":status: 200"
+
+            HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders, 0, 0, 0, streamId);
+            await WriteFrameAsync(headersFrame).ConfigureAwait(false);
+        }
+
+        public async Task SendDefaultResponseAsync(int streamId)
+        {
+            byte[] headers = new byte[] { 0x88 };   // Encoding for ":status: 200"
+
+            HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders | FrameFlags.EndStream, 0, 0, 0, streamId);
+            await WriteFrameAsync(headersFrame).ConfigureAwait(false);
+        }
+
+        public async Task SendResponseHeadersAsync(int streamId, bool endStream = true, HttpStatusCode statusCode = HttpStatusCode.OK, bool isTrailingHeader = false, IList<HttpHeaderData> headers = null)
+        {
+            // For now, only support headers that fit in a single frame
+            byte[] headerBlock = new byte[Frame.MaxFrameLength];
+            int bytesGenerated = 0;
+
+            if (!isTrailingHeader)
+            {
+                string statusCodeString = ((int)statusCode).ToString();
+                bytesGenerated += EncodeHeader(new HttpHeaderData(":status", statusCodeString), headerBlock.AsSpan());
+            }
+
+            if (headers != null)
+            {
+                foreach (HttpHeaderData headerData in headers)
+                {
+                    bytesGenerated += EncodeHeader(headerData, headerBlock.AsSpan().Slice(bytesGenerated));
+                }
+            }
+
+            FrameFlags flags = FrameFlags.EndHeaders;
+            if (endStream)
+            {
+                flags |= FrameFlags.EndStream;
+            }
+
+            HeadersFrame headersFrame = new HeadersFrame(headerBlock.AsMemory().Slice(0, bytesGenerated), flags, 0, 0, 0, streamId);
+            await WriteFrameAsync(headersFrame).ConfigureAwait(false);
+        }
+
+        public async Task SendResponseDataAsync(int streamId, ReadOnlyMemory<byte> responseData, bool endStream)
+        {
+            DataFrame dataFrame = new DataFrame(responseData, endStream ? FrameFlags.EndStream : FrameFlags.None, 0, streamId);
+            await WriteFrameAsync(dataFrame).ConfigureAwait(false);
+        }
+
+        public async Task SendResponseBodyAsync(int streamId, ReadOnlyMemory<byte> responseBody)
+        {
+            // Only support response body if it fits in a single frame, for now
+            // In the future we should separate the body into chunks as needed,
+            // and if it's larger than the default window size, we will need to process window updates as well.
+            if (responseBody.Length > Frame.MaxFrameLength)
+            {
+                throw new Exception("Response body too long");
+            }
+
+            await SendResponseDataAsync(streamId, responseBody, true).ConfigureAwait(false);
+        }
+    }
+}
index 10bfcd1..a8d7ad6 100644 (file)
@@ -18,13 +18,22 @@ namespace System.Net.Test.Common
     public class Http2LoopbackServer : GenericLoopbackServer, IDisposable
     {
         private Socket _listenSocket;
-        private Socket _connectionSocket;
-        private Stream _connectionStream;
         private Http2Options _options;
         private Uri _uri;
-        private bool _ignoreSettingsAck;
-        private bool _ignoreWindowUpdates;
-        public TimeSpan Timeout = TimeSpan.FromSeconds(30);
+        private List<Http2LoopbackConnection> _connections = new List<Http2LoopbackConnection>();
+
+        public bool AllowMultipleConnections { get; set; }
+
+        private Http2LoopbackConnection Connection
+        {
+            get
+            {
+                RemoveInvalidConnections();
+                return _connections[0];
+            }
+        }
+
+        public static readonly TimeSpan Timeout = TimeSpan.FromSeconds(30);
 
         public Uri Address
         {
@@ -61,198 +70,40 @@ namespace System.Net.Test.Common
             _listenSocket.Listen(_options.ListenBacklog);
         }
 
-        public async Task SendConnectionPrefaceAsync()
-        {
-            // Send the initial server settings frame.
-            Frame emptySettings = new Frame(0, FrameType.Settings, FrameFlags.None, 0);
-            await WriteFrameAsync(emptySettings).ConfigureAwait(false);
-
-            // Receive and ACK the client settings frame.
-            Frame clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
-            clientSettings.Flags = clientSettings.Flags | FrameFlags.Ack;
-            await WriteFrameAsync(clientSettings).ConfigureAwait(false);
-
-            // Receive the client ACK of the server settings frame.
-            clientSettings = await ReadFrameAsync(Timeout).ConfigureAwait(false);
-        }
-
-        public async Task WriteFrameAsync(Frame frame)
-        {
-            byte[] writeBuffer = new byte[Frame.FrameHeaderLength + frame.Length];
-            frame.WriteTo(writeBuffer);
-            await _connectionStream.WriteAsync(writeBuffer, 0, writeBuffer.Length).ConfigureAwait(false);
-        }
-
-        // Read until the buffer is full
-        // Return false on EOF, throw on partial read
-        private async Task<bool> FillBufferAsync(Memory<byte> buffer, CancellationToken cancellationToken = default(CancellationToken))
-        {
-            int readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
-            if (readBytes == 0)
-            {
-                return false;
-            }
-
-            buffer = buffer.Slice(readBytes);
-            while (buffer.Length > 0)
-            {
-                readBytes = await _connectionStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
-                if (readBytes == 0)
-                {
-                    throw new Exception("Connection closed when expecting more data.");
-                }
-
-                buffer = buffer.Slice(readBytes);
-            }
-
-            return true;
-        }
-
-        public async Task<Frame> ReadFrameAsync(TimeSpan timeout)
-        {
-            using CancellationTokenSource timeoutCts = new CancellationTokenSource(timeout);
-            return await ReadFrameAsync(timeoutCts.Token);
-        }
-
-        private async Task<Frame> ReadFrameAsync(CancellationToken cancellationToken)
-        {
-            // Prep the timeout cancellation token.
-
-            // First read the frame headers, which should tell us how long the rest of the frame is.
-            byte[] headerBytes = new byte[Frame.FrameHeaderLength];
-
-            try
-            {
-                if (!await FillBufferAsync(headerBytes, cancellationToken).ConfigureAwait(false))
-                {
-                    return null;
-                }
-            }
-            catch (IOException)
-            {
-                // eat errors when client aborts connection and return null.
-                return null;
-            }
-
-            Frame header = Frame.ReadFrom(headerBytes);
-
-            // Read the data segment of the frame, if it is present.
-            byte[] data = new byte[header.Length];
-            if (header.Length > 0 && !await FillBufferAsync(data, cancellationToken).ConfigureAwait(false))
-            {
-                throw new Exception("Connection stream closed while attempting to read frame body.");
-            }
-
-            if (_ignoreSettingsAck && header.Type == FrameType.Settings && header.Flags == FrameFlags.Ack)
-            {
-                _ignoreSettingsAck = false;
-                return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
-            }
-
-            if (_ignoreWindowUpdates && header.Type == FrameType.WindowUpdate)
-            {
-                return await ReadFrameAsync(cancellationToken).ConfigureAwait(false);
-            }
-
-            // Construct the correct frame type and return it.
-            switch (header.Type)
-            {
-                case FrameType.Settings:
-                    return SettingsFrame.ReadFrom(header, data);
-                case FrameType.Data:
-                    return DataFrame.ReadFrom(header, data);
-                case FrameType.Headers:
-                    return HeadersFrame.ReadFrom(header, data);
-                case FrameType.Priority:
-                    return PriorityFrame.ReadFrom(header, data);
-                case FrameType.RstStream:
-                    return RstStreamFrame.ReadFrom(header, data);
-                case FrameType.Ping:
-                    return PingFrame.ReadFrom(header, data);
-                case FrameType.GoAway:
-                    return GoAwayFrame.ReadFrom(header, data);
-                default:
-                    return header;
-            }
-        }
-
-        // Reset and return underlying networking objects.
-        public (Socket, Stream) ResetNetwork()
+        private void RemoveInvalidConnections()
         {
-            Socket oldSocket = _connectionSocket;
-            Stream oldStream = _connectionStream;
-            _connectionSocket = null;
-            _connectionStream = null;
-            _ignoreSettingsAck = false;
-
-            return (oldSocket, oldStream);
+            _connections.RemoveAll((c) => c.IsInvalid);
         }
 
-        // Returns the first 24 bytes read, which should be the connection preface.
-        public async Task<string> AcceptConnectionAsync()
+        public async Task<Http2LoopbackConnection> AcceptConnectionAsync()
         {
-            if (_connectionSocket != null)
-            {
-                throw new InvalidOperationException("Connection already established");
-            }
-
-            _connectionSocket = await _listenSocket.AcceptAsync().ConfigureAwait(false);
-            _connectionStream = new NetworkStream(_connectionSocket, true);
+            RemoveInvalidConnections();
 
-            if (_options.UseSsl)
+            if (!AllowMultipleConnections && _connections.Count != 0)
             {
-                var sslStream = new SslStream(_connectionStream, false, delegate { return true; });
-                using (var cert = Configuration.Certificates.GetServerCertificate())
-                {
-                    SslServerAuthenticationOptions options = new SslServerAuthenticationOptions();
-
-                    options.EnabledSslProtocols = _options.SslProtocols;
-
-                    var protocols = new List<SslApplicationProtocol>();
-                    protocols.Add(SslApplicationProtocol.Http2);
-                    protocols.Add(SslApplicationProtocol.Http11);
-                    options.ApplicationProtocols = protocols;
-
-                    options.ServerCertificate = cert;
-
-                    options.ClientCertificateRequired = false;
-
-                    await sslStream.AuthenticateAsServerAsync(options, CancellationToken.None).ConfigureAwait(false);
-                }
-                _connectionStream = sslStream;
-            }
-
-            byte[] prefix = new byte[24];
-            if (!await FillBufferAsync(prefix).ConfigureAwait(false))
-            {
-                throw new Exception("Connection stream closed while attempting to read connection preface.");
+                throw new InvalidOperationException("Connection already established. Set `AllowMultipleConnections = true` to bypass.");
             }
 
-            return System.Text.Encoding.UTF8.GetString(prefix, 0, prefix.Length);
-        }
+            Socket connectionSocket = await _listenSocket.AcceptAsync().ConfigureAwait(false);
 
-        public void ExpectSettingsAck()
-        {
-            // The timing of when we receive the settings ack is not guaranteed.
-            // To simplify frame processing, just record that we are expecting one,
-            // and then filter it out in ReadFrameAsync above.
+            Http2LoopbackConnection connection = new Http2LoopbackConnection(connectionSocket, _options);
+            _connections.Add(connection);
 
-            Assert.False(_ignoreSettingsAck);
-            _ignoreSettingsAck = true;
+            return connection;
         }
 
-        public void IgnoreWindowUpdates()
+        public async Task<Http2LoopbackConnection> EstablishConnectionAsync(params SettingsEntry[] settingsEntries)
         {
-            _ignoreWindowUpdates = true;
+            (Http2LoopbackConnection connection, _) = await EstablishConnectionGetSettingsAsync();
+            return connection;
         }
 
-        // Accept connection and handle connection setup
-        public async Task<SettingsFrame> EstablishConnectionAsync(params SettingsEntry[] settingsEntries)
+        public async Task<(Http2LoopbackConnection, SettingsFrame)> EstablishConnectionGetSettingsAsync(params SettingsEntry[] settingsEntries)
         {
-            await AcceptConnectionAsync().ConfigureAwait(false);
+            Http2LoopbackConnection connection = await AcceptConnectionAsync().ConfigureAwait(false);
 
             // Receive the initial client settings frame.
-            Frame receivedFrame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+            Frame receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
             Assert.Equal(FrameType.Settings, receivedFrame.Type);
             Assert.Equal(FrameFlags.None, receivedFrame.Flags);
             Assert.Equal(0, receivedFrame.StreamId);
@@ -260,424 +111,23 @@ namespace System.Net.Test.Common
             var clientSettingsFrame = (SettingsFrame)receivedFrame;
 
             // Receive the initial client window update frame.
-            receivedFrame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
+            receivedFrame = await connection.ReadFrameAsync(Timeout).ConfigureAwait(false);
             Assert.Equal(FrameType.WindowUpdate, receivedFrame.Type);
             Assert.Equal(FrameFlags.None, receivedFrame.Flags);
             Assert.Equal(0, receivedFrame.StreamId);
 
             // Send the initial server settings frame.
             SettingsFrame settingsFrame = new SettingsFrame(settingsEntries);
-            await WriteFrameAsync(settingsFrame).ConfigureAwait(false);
+            await connection.WriteFrameAsync(settingsFrame).ConfigureAwait(false);
 
             // Send the client settings frame ACK.
             Frame settingsAck = new Frame(0, FrameType.Settings, FrameFlags.Ack, 0);
-            await WriteFrameAsync(settingsAck).ConfigureAwait(false);
+            await connection.WriteFrameAsync(settingsAck).ConfigureAwait(false);
 
             // The client will send us a SETTINGS ACK eventually, but not necessarily right away.
-            ExpectSettingsAck();
-
-            return clientSettingsFrame;
-        }
-
-        public void ShutdownSend()
-        {
-            _connectionSocket.Shutdown(SocketShutdown.Send);
-        }
-
-        // This will wait for the client to close the connection,
-        // and ignore any meaningless frames -- i.e. WINDOW_UPDATE or expected SETTINGS ACK --
-        // that we see while waiting for the client to close.
-        // Only call this after sending a GOAWAY.
-        public async Task WaitForConnectionShutdownAsync()
-        {
-            // Shutdown our send side, so the client knows there won't be any more frames coming.
-            ShutdownSend();
-
-            IgnoreWindowUpdates();
-            Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
-            if (frame != null)
-            {
-                throw new Exception($"Unexpected frame received while waiting for client shutdown: {frame}");
-            }
-
-            _connectionStream.Close();
-
-            _connectionSocket = null;
-            _connectionStream = null;
-
-            _ignoreSettingsAck = false;
-            _ignoreWindowUpdates = false;
-        }
-
-        public async Task<int> ReadRequestHeaderAsync()
-        {
-            // Receive HEADERS frame for request.
-            Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
-            if (frame == null)
-            {
-                throw new IOException("Failed to read Headers frame.");
-            }
-            Assert.Equal(FrameType.Headers, frame.Type);
-            Assert.Equal(FrameFlags.EndHeaders | FrameFlags.EndStream, frame.Flags);
-            return frame.StreamId;
-        }
-
-        private static (int bytesConsumed, int value) DecodeInteger(ReadOnlySpan<byte> headerBlock, byte prefixMask)
-        {
-            int value = headerBlock[0] & prefixMask;
-            if (value != prefixMask)
-            {
-                return (1, value);
-            }
-
-            byte b = headerBlock[1];
-            if ((b & 0b10000000) != 0)
-            {
-                throw new Exception("long integers currently not supported");
-            }
-            return (2, prefixMask + b);
-        }
-
-        private static int EncodeInteger(int value, byte prefix, byte prefixMask, Span<byte> headerBlock)
-        {
-            byte prefixLimit = (byte)(~prefixMask);
-
-            if (value < prefixLimit)
-            {
-                headerBlock[0] = (byte)(prefix | value);
-                return 1;
-            }
-
-            headerBlock[0] = (byte)(prefix | prefixLimit);
-            int bytesGenerated = 1;
-
-            value -= prefixLimit;
-
-            while (value >= 0x80)
-            {
-                headerBlock[bytesGenerated] = (byte)((value & 0x7F) | 0x80);
-                value = value >> 7;
-                bytesGenerated++;
-            }
-
-            headerBlock[bytesGenerated] = (byte)value;
-            bytesGenerated++;
-
-            return bytesGenerated;
-        }
-
-        private static (int bytesConsumed, string value) DecodeString(ReadOnlySpan<byte> headerBlock)
-        {
-            (int bytesConsumed, int stringLength) = DecodeInteger(headerBlock, 0b01111111);
-            if ((headerBlock[0] & 0b10000000) != 0)
-            {
-                // Huffman encoded
-                byte[] buffer = new byte[stringLength * 2];
-                int bytesDecoded = HuffmanDecoder.Decode(headerBlock.Slice(bytesConsumed, stringLength), buffer);
-                string value = Encoding.ASCII.GetString(buffer, 0, bytesDecoded);
-                return (bytesConsumed + stringLength, value);
-            }
-            else
-            {
-                string value = Encoding.ASCII.GetString(headerBlock.Slice(bytesConsumed, stringLength));
-                return (bytesConsumed + stringLength, value);
-            }
-        }
-
-        private static int EncodeString(string value, Span<byte> headerBlock)
-        {
-            int bytesGenerated = EncodeInteger(value.Length, 0, 0x80, headerBlock);
-
-            bytesGenerated += Encoding.ASCII.GetBytes(value.AsSpan(), headerBlock.Slice(bytesGenerated));
-
-            return bytesGenerated;
-        }
-
-        private static readonly HttpHeaderData[] s_staticTable = new HttpHeaderData[]
-        {
-            new HttpHeaderData(":authority", ""),
-            new HttpHeaderData(":method", "GET"),
-            new HttpHeaderData(":method", "POST"),
-            new HttpHeaderData(":path", "/"),
-            new HttpHeaderData(":path", "/index.html"),
-            new HttpHeaderData(":scheme", "http"),
-            new HttpHeaderData(":scheme", "https"),
-            new HttpHeaderData(":status", "200"),
-            new HttpHeaderData(":status", "204"),
-            new HttpHeaderData(":status", "206"),
-            new HttpHeaderData(":status", "304"),
-            new HttpHeaderData(":status", "400"),
-            new HttpHeaderData(":status", "404"),
-            new HttpHeaderData(":status", "500"),
-            new HttpHeaderData("accept-charset", ""),
-            new HttpHeaderData("accept-encoding", "gzip, deflate"),
-            new HttpHeaderData("accept-language", ""),
-            new HttpHeaderData("accept-ranges", ""),
-            new HttpHeaderData("accept", ""),
-            new HttpHeaderData("access-control-allow-origin", ""),
-            new HttpHeaderData("age", ""),
-            new HttpHeaderData("allow", ""),
-            new HttpHeaderData("authorization", ""),
-            new HttpHeaderData("cache-control", ""),
-            new HttpHeaderData("content-disposition", ""),
-            new HttpHeaderData("content-encoding", ""),
-            new HttpHeaderData("content-language", ""),
-            new HttpHeaderData("content-length", ""),
-            new HttpHeaderData("content-location", ""),
-            new HttpHeaderData("content-range", ""),
-            new HttpHeaderData("content-type", ""),
-            new HttpHeaderData("cookie", ""),
-            new HttpHeaderData("date", ""),
-            new HttpHeaderData("etag", ""),
-            new HttpHeaderData("expect", ""),
-            new HttpHeaderData("expires", ""),
-            new HttpHeaderData("from", ""),
-            new HttpHeaderData("host", ""),
-            new HttpHeaderData("if-match", ""),
-            new HttpHeaderData("if-modified-since", ""),
-            new HttpHeaderData("if-none-match", ""),
-            new HttpHeaderData("if-range", ""),
-            new HttpHeaderData("if-unmodified-since", ""),
-            new HttpHeaderData("last-modified", ""),
-            new HttpHeaderData("link", ""),
-            new HttpHeaderData("location", ""),
-            new HttpHeaderData("max-forwards", ""),
-            new HttpHeaderData("proxy-authenticate", ""),
-            new HttpHeaderData("proxy-authorization", ""),
-            new HttpHeaderData("range", ""),
-            new HttpHeaderData("referer", ""),
-            new HttpHeaderData("refresh", ""),
-            new HttpHeaderData("retry-after", ""),
-            new HttpHeaderData("server", ""),
-            new HttpHeaderData("set-cookie", ""),
-            new HttpHeaderData("strict-transport-security", ""),
-            new HttpHeaderData("transfer-encoding", ""),
-            new HttpHeaderData("user-agent", ""),
-            new HttpHeaderData("vary", ""),
-            new HttpHeaderData("via", ""),
-            new HttpHeaderData("www-authenticate", "")
-        };
-
-        private static HttpHeaderData GetHeaderForIndex(int index)
-        {
-            return s_staticTable[index - 1];
-        }
-
-        private static (int bytesConsumed, HttpHeaderData headerData) DecodeLiteralHeader(ReadOnlySpan<byte> headerBlock, byte prefixMask)
-        {
-            int i = 0;
-
-            (int bytesConsumed, int index) = DecodeInteger(headerBlock, prefixMask);
-            i += bytesConsumed;
-
-            string name;
-            if (index == 0)
-            {
-                (bytesConsumed, name) = DecodeString(headerBlock.Slice(i));
-                i += bytesConsumed;
-            }
-            else
-            {
-                name = GetHeaderForIndex(index).Name;
-            }
-
-            string value;
-            (bytesConsumed, value) = DecodeString(headerBlock.Slice(i));
-            i += bytesConsumed;
-
-            return (i, new HttpHeaderData(name, value));
-        }
-
-        private static (int bytesConsumed, HttpHeaderData headerData) DecodeHeader(ReadOnlySpan<byte> headerBlock)
-        {
-            int i = 0;
-
-            byte b = headerBlock[0];
-            if ((b & 0b10000000) != 0)
-            {
-                // Indexed header
-                (int bytesConsumed, int index) = DecodeInteger(headerBlock, 0b01111111);
-                i += bytesConsumed;
-
-                return (i, GetHeaderForIndex(index));
-            }
-            else if ((b & 0b11000000) == 0b01000000)
-            {
-                // Literal with indexing
-                return DecodeLiteralHeader(headerBlock, 0b00111111);
-            }
-            else if ((b & 0b11100000) == 0b00100000)
-            {
-                // Table size update
-                throw new Exception("table size update not supported");
-            }
-            else
-            {
-                // Literal, never indexed
-                return DecodeLiteralHeader(headerBlock, 0b00001111);
-            }
-        }
-
-        private static int EncodeHeader(HttpHeaderData headerData, Span<byte> headerBlock)
-        {
-            // Always encode as literal, no indexing
-            int bytesGenerated = EncodeInteger(0, 0, 0b11110000, headerBlock);
-            bytesGenerated += EncodeString(headerData.Name, headerBlock.Slice(bytesGenerated));
-            bytesGenerated += EncodeString(headerData.Value, headerBlock.Slice(bytesGenerated));
-            return bytesGenerated;
-        }
-
-        public async Task<byte[]> ReadBodyAsync()
-        {
-            byte[] body = null;
-            Frame frame;
-
-            do
-            {
-                frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
-                if (frame == null || frame.Type == FrameType.RstStream)
-                {
-                    throw new IOException( frame == null ? "End of stream" : "Got RST");
-                }
-
-                Assert.Equal(FrameType.Data, frame.Type);
-
-                if (frame.Length > 1)
-                {
-                    DataFrame dataFrame = (DataFrame)frame;
-
-                    if (body == null)
-                    {
-                        body = dataFrame.Data.ToArray();
-                    }
-                    else
-                    {
-                        byte[] newBuffer = new byte[body.Length + dataFrame.Data.Length];
-
-                        body.CopyTo(newBuffer, 0);
-                        dataFrame.Data.Span.CopyTo(newBuffer.AsSpan().Slice(body.Length));
-                        body= newBuffer;
-                    }
-                }
-            }
-            while ((frame.Flags & FrameFlags.EndStream) == 0);
-
-            return body;
-        }
-
-        public async Task<(int streamId, HttpRequestData requestData)> ReadAndParseRequestHeaderAsync(bool readBody = true)
-        {
-            HttpRequestData requestData = new HttpRequestData();
-
-            // Receive HEADERS frame for request.
-            Frame frame = await ReadFrameAsync(Timeout).ConfigureAwait(false);
-            if (frame == null)
-            {
-                throw new IOException("Failed to read Headers frame.");
-            }
-            Assert.Equal(FrameType.Headers, frame.Type);
-            HeadersFrame headersFrame = (HeadersFrame) frame;
-
-            // TODO CONTINUATION support
-            Assert.Equal(FrameFlags.EndHeaders, FrameFlags.EndHeaders & headersFrame.Flags);
-
-            int streamId = headersFrame.StreamId;
-
-            Memory<byte> data = headersFrame.Data;
-            int i = 0;
-            while (i < data.Length)
-            {
-                (int bytesConsumed, HttpHeaderData headerData) = DecodeHeader(data.Span.Slice(i));
-
-                byte[] headerRaw = data.Span.Slice(i, bytesConsumed).ToArray();
-                headerData = new HttpHeaderData(headerData.Name, headerData.Value, headerRaw);
-
-                requestData.Headers.Add(headerData);
-                i += bytesConsumed;
-            }
-
-            // Extract method and path
-            requestData.Method = requestData.GetSingleHeaderValue(":method");
-            requestData.Path = requestData.GetSingleHeaderValue(":path");
-
-            if (readBody && (frame.Flags & FrameFlags.EndStream) == 0)
-            {
-                // Read body until end of stream if needed.
-                requestData.Body = await ReadBodyAsync();
-            }
-
-            return (streamId, requestData);
-        }
-
-        public async Task SendGoAway(int lastStreamId)
-        {
-            GoAwayFrame frame = new GoAwayFrame(lastStreamId, 0, new byte[] { }, 0);
-            await WriteFrameAsync(frame).ConfigureAwait(false);
-        }
-
-        public async Task SendDefaultResponseHeadersAsync(int streamId)
-        {
-            byte[] headers = new byte[] { 0x88 };   // Encoding for ":status: 200"
-
-            HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders, 0, 0, 0, streamId);
-            await WriteFrameAsync(headersFrame).ConfigureAwait(false);
-        }
-
-        public async Task SendDefaultResponseAsync(int streamId)
-        {
-            byte[] headers = new byte[] { 0x88 };   // Encoding for ":status: 200"
-
-            HeadersFrame headersFrame = new HeadersFrame(headers, FrameFlags.EndHeaders | FrameFlags.EndStream, 0, 0, 0, streamId);
-            await WriteFrameAsync(headersFrame).ConfigureAwait(false);
-        }
-
-        public async Task SendResponseHeadersAsync(int streamId, bool endStream = true, HttpStatusCode statusCode = HttpStatusCode.OK, bool isTrailingHeader = false, IList<HttpHeaderData> headers = null)
-        {
-            // For now, only support headers that fit in a single frame
-            byte[] headerBlock = new byte[Frame.MaxFrameLength];
-            int bytesGenerated = 0;
-
-            if (!isTrailingHeader)
-            {
-                string statusCodeString = ((int)statusCode).ToString();
-                bytesGenerated += EncodeHeader(new HttpHeaderData(":status", statusCodeString), headerBlock.AsSpan());
-            }
-
-            if (headers != null)
-            {
-                foreach (HttpHeaderData headerData in headers)
-                {
-                    bytesGenerated += EncodeHeader(headerData, headerBlock.AsSpan().Slice(bytesGenerated));
-                }
-            }
-
-            FrameFlags flags = FrameFlags.EndHeaders;
-            if (endStream)
-            {
-                flags |= FrameFlags.EndStream;
-            }
-
-            HeadersFrame headersFrame = new HeadersFrame(headerBlock.AsMemory().Slice(0, bytesGenerated), flags, 0, 0, 0, streamId);
-            await WriteFrameAsync(headersFrame).ConfigureAwait(false);
-        }
-
-        public async Task SendResponseDataAsync(int streamId, ReadOnlyMemory<byte> responseData, bool endStream)
-        {
-            DataFrame dataFrame = new DataFrame(responseData, endStream ? FrameFlags.EndStream : FrameFlags.None, 0, streamId);
-            await WriteFrameAsync(dataFrame).ConfigureAwait(false);
-        }
-
-        public async Task SendResponseBodyAsync(int streamId, ReadOnlyMemory<byte> responseBody)
-        {
-            // Only support response body if it fits in a single frame, for now
-            // In the future we should separate the body into chunks as needed,
-            // and if it's larger than the default window size, we will need to process window updates as well.
-            if (responseBody.Length > Frame.MaxFrameLength)
-            {
-                throw new Exception("Response body too long");
-            }
+            connection.ExpectSettingsAck();
 
-            await SendResponseDataAsync(streamId, responseBody, true).ConfigureAwait(false);
+            return (connection, clientSettingsFrame);
         }
 
         public override void Dispose()
@@ -695,25 +145,25 @@ namespace System.Net.Test.Common
 
         public override async Task<HttpRequestData> HandleRequestAsync(HttpStatusCode statusCode = HttpStatusCode.OK, IList<HttpHeaderData> headers = null, string content = null)
         {
-            await EstablishConnectionAsync().ConfigureAwait(false);
+            Http2LoopbackConnection connection = await EstablishConnectionAsync().ConfigureAwait(false);
 
-            (int streamId, HttpRequestData requestData) = await ReadAndParseRequestHeaderAsync().ConfigureAwait(false);
+            (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync().ConfigureAwait(false);
 
             // We are about to close the connection, after we send the response.
             // So, send a GOAWAY frame now so the client won't inadvertantly try to reuse the connection.
-            await SendGoAway(streamId).ConfigureAwait(false);
+            await connection.SendGoAway(streamId).ConfigureAwait(false);
 
             if (content == null)
             {
-                await SendResponseHeadersAsync(streamId, endStream: true, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
+                await connection.SendResponseHeadersAsync(streamId, endStream: true, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
             }
             else
             {
-                await SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
-                await SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes(content)).ConfigureAwait(false);
+                await connection.SendResponseHeadersAsync(streamId, endStream: false, statusCode, isTrailingHeader: false, headers).ConfigureAwait(false);
+                await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes(content)).ConfigureAwait(false);
             }
 
-            await WaitForConnectionShutdownAsync().ConfigureAwait(false);
+            await connection.WaitForConnectionShutdownAsync().ConfigureAwait(false);
 
             return requestData;
         }
index 521d807..d08d617 100644 (file)
@@ -51,14 +51,14 @@ namespace System.Net.Http.Functional.Tests
                 },
                 async server =>
                 {
-                    await server.EstablishConnectionAsync();
-                    (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync();
+                    Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                    (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync();
 
                     HttpHeaderData header = requestData.Headers.Single(x => x.Name == headerName);
                     Assert.Equal(expectedValue, header.Value);
                     Assert.True(expectedEncoding.AsSpan().SequenceEqual(header.Raw));
 
-                    await server.SendDefaultResponseAsync(streamId);
+                    await connection.SendDefaultResponseAsync(streamId);
                 });
         }
 
index 2aaae6b..5281fc4 100644 (file)
@@ -65,7 +65,7 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task sendTask = client.GetAsync(server.Address);
 
-                string connectionPreface = await server.AcceptConnectionAsync();
+                string connectionPreface = (await server.AcceptConnectionAsync()).PrefixString;
 
                 Assert.Equal("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", connectionPreface);
             }
@@ -79,22 +79,22 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task sendTask = client.GetAsync(server.Address);
 
-                await server.AcceptConnectionAsync();
+                Http2LoopbackConnection connection = await server.AcceptConnectionAsync();
 
                 // Receive the initial client settings frame.
-                Frame receivedFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Frame receivedFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 Assert.Equal(FrameType.Settings, receivedFrame.Type);
 
                 // Send the initial server settings frame.
                 Frame emptySettings = new Frame(0, FrameType.Settings, FrameFlags.None, 0);
-                await server.WriteFrameAsync(emptySettings).ConfigureAwait(false);
+                await connection.WriteFrameAsync(emptySettings).ConfigureAwait(false);
 
                 // Receive the server settings frame ACK.
                 // This doesn't have to be the next frame, as the client is allowed to send before receiving our SETTINGS frame.
                 // So, loop until we see it (or the timeout expires)
                 while (true)
                 {
-                    receivedFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                    receivedFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                     if (receivedFrame.Type == FrameType.Settings && receivedFrame.AckFlag)
                     {
                         break;
@@ -111,11 +111,11 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task sendTask = client.GetAsync(server.Address);
 
-                await server.AcceptConnectionAsync();
+                Http2LoopbackConnection connection = await server.AcceptConnectionAsync();
 
                 // Send a frame despite not having sent the server connection preface.
                 DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.Padded, 10, 1);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
             }
@@ -129,11 +129,11 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -149,15 +149,15 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
 
                 // Send zero-length body
                 var frame = new DataFrame(new byte[0], FrameFlags.EndStream, 0, streamId);
-                await server.WriteFrameAsync(frame);
+                await connection.WriteFrameAsync(frame);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -174,7 +174,7 @@ namespace System.Net.Http.Functional.Tests
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
                 // Send a bunch of valid SETTINGS values (that won't interfere with processing requests)
-                await server.EstablishConnectionAsync(
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync(
                     new SettingsEntry { SettingId = SettingId.HeaderTableSize, Value = 0 },
                     new SettingsEntry { SettingId = SettingId.HeaderTableSize, Value = 1 },
                     new SettingsEntry { SettingId = SettingId.HeaderTableSize, Value = 345678 },
@@ -188,9 +188,9 @@ namespace System.Net.Http.Functional.Tests
                     new SettingsEntry { SettingId = SettingId.MaxHeaderListSize, Value = 10000000 },
                     new SettingsEntry { SettingId = (SettingId)5678, Value = 1234 });
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -237,12 +237,12 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Send a reset stream frame so that the stream moves to a terminal state.
                 RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
-                await server.WriteFrameAsync(resetStream);
+                await connection.WriteFrameAsync(resetStream);
 
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
             }
@@ -256,15 +256,15 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Send response headers
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
 
                 // Send a reset stream frame so that the stream moves to a terminal state.
                 RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
-                await server.WriteFrameAsync(resetStream);
+                await connection.WriteFrameAsync(resetStream);
 
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
             }
@@ -278,17 +278,17 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Send response headers and partial response body
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
                 DataFrame dataFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
-                await server.WriteFrameAsync(dataFrame);
+                await connection.WriteFrameAsync(dataFrame);
 
                 // Send a reset stream frame so that the stream moves to a terminal state.
                 RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)ProtocolErrors.INTERNAL_ERROR, streamId);
-                await server.WriteFrameAsync(resetStream);
+                await connection.WriteFrameAsync(resetStream);
 
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.INTERNAL_ERROR);
             }
@@ -305,18 +305,18 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                await connection.ReadRequestHeaderAsync();
 
                 // Send a malformed frame (streamId is 0)
                 DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, 0);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -337,18 +337,18 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                await connection.ReadRequestHeaderAsync();
 
                 // Send a data frame on stream 5, which is in the idle state.
                 DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, 5);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -369,17 +369,17 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Send a headers frame on stream 5, which is in the idle state.
-                await server.SendDefaultResponseHeadersAsync(5);
+                await connection.SendDefaultResponseHeadersAsync(5);
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -405,16 +405,16 @@ namespace System.Net.Http.Functional.Tests
             using (HttpClient client = CreateHttpClient())
             {
                 Task sendTask = client.GetAsync(server.Address);
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId));
+                await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId));
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -425,16 +425,16 @@ namespace System.Net.Http.Functional.Tests
             using (HttpClient client = CreateHttpClient())
             {
                 Task sendTask = client.GetAsync(server.Address);
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
+                await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId));
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -445,17 +445,17 @@ namespace System.Net.Http.Functional.Tests
             using (HttpClient client = CreateHttpClient())
             {
                 Task sendTask = client.GetAsync(server.Address);
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
-                await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -466,18 +466,18 @@ namespace System.Net.Http.Functional.Tests
             using (HttpClient client = CreateHttpClient())
             {
                 Task sendTask = client.GetAsync(server.Address);
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
-                await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
-                await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -488,17 +488,17 @@ namespace System.Net.Http.Functional.Tests
             using (HttpClient client = CreateHttpClient())
             {
                 Task sendTask = client.GetAsync(server.Address);
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
-                await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
+                await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId));
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -509,18 +509,18 @@ namespace System.Net.Http.Functional.Tests
             using (HttpClient client = CreateHttpClient())
             {
                 Task sendTask = client.GetAsync(server.Address);
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
-                await server.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
-                await server.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
-                await server.WriteFrameAsync(MakeSimpleDataFrame(streamId));
+                await connection.WriteFrameAsync(MakeSimpleHeadersFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleContinuationFrame(streamId, endHeaders: false));
+                await connection.WriteFrameAsync(MakeSimpleDataFrame(streamId));
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
             }
         }
 
@@ -535,18 +535,49 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                await connection.ReadRequestHeaderAsync();
 
                 // Send a GoAway frame on stream 1.
                 GoAwayFrame invalidFrame = new GoAwayFrame(0, (int)ProtocolErrors.ENHANCE_YOUR_CALM, new byte[0], 1);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.PROTOCOL_ERROR);
 
                 // The client should close the connection as this is a fatal connection level error.
-                Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+            }
+        }
+
+        [ConditionalFact(nameof(SupportsAlpn))]
+        public async Task GoAwayFrame_NewRequest_NewConnection()
+        {
+            using (var server = Http2LoopbackServer.CreateServer())
+            using (HttpClient client = CreateHttpClient())
+            {
+                server.AllowMultipleConnections = true;
+
+                Task<HttpResponseMessage> sendTask1 = client.GetAsync(server.Address);
+                Http2LoopbackConnection connection1 = await server.EstablishConnectionAsync();
+                int streamId1 = await connection1.ReadRequestHeaderAsync();
+
+                await connection1.SendGoAway(streamId1);
+
+                await connection1.SendDefaultResponseAsync(streamId1);
+                HttpResponseMessage response1 = await sendTask1;
+                Assert.Equal(HttpStatusCode.OK, response1.StatusCode);
+
+                // New connection should be established after GOAWAY
+                Task<HttpResponseMessage> sendTask2 = client.GetAsync(server.Address);
+                Http2LoopbackConnection connection2 = await server.EstablishConnectionAsync();
+                int streamId2 = await connection2.ReadRequestHeaderAsync();
+                await connection2.SendDefaultResponseAsync(streamId2);
+                HttpResponseMessage response2 = await sendTask2;
+                Assert.Equal(HttpStatusCode.OK, response2.StatusCode);
+
+                await connection1.WaitForConnectionShutdownAsync();
+                await connection2.WaitForConnectionShutdownAsync();
             }
         }
 
@@ -558,12 +589,12 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                await connection.ReadRequestHeaderAsync();
 
                 // Send a malformed frame.
                 DataFrame invalidFrame = new DataFrame(new byte[Frame.MaxFrameLength + 1], FrameFlags.None, 0, 0);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 // As this is a connection level error, the client should see the request fail.
                 await AssertProtocolErrorAsync(sendTask, ProtocolErrors.FRAME_SIZE_ERROR);
@@ -578,26 +609,26 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                server.IgnoreWindowUpdates();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                connection.IgnoreWindowUpdates();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Send response and end stream.
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
                 DataFrame dataFrame = new DataFrame(new byte[10], FrameFlags.EndStream, 0, streamId);
-                await server.WriteFrameAsync(dataFrame);
+                await connection.WriteFrameAsync(dataFrame);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 
                 // Send a frame on the now-closed stream.
                 DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 if (!IsWinHttpHandler)
                 {
                     // The client should close the connection as this is a fatal connection level error.
-                    Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                    Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
                 }
             }
         }
@@ -610,23 +641,23 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Send empty response.
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 
                 // Send a frame on the now-closed stream.
                 DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 if (!IsWinHttpHandler)
                 {
                     // The client should close the connection as this is a fatal connection level error.
-                    Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                    Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
                 }
             }
         }
@@ -639,21 +670,21 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Send empty response.
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 
                 // Send a frame on the now-closed stream.
                 WindowUpdateFrame invalidFrame = new WindowUpdateFrame(1, streamId);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 // The client should close the connection.
-                await server.WaitForConnectionShutdownAsync();
+                await connection.WaitForConnectionShutdownAsync();
             }
         }
 
@@ -672,33 +703,35 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
+                await connection.SendDefaultResponseHeadersAsync(streamId);
 
                 // Send a reset stream frame so that stream 1 moves to a terminal state.
                 RstStreamFrame resetStream = new RstStreamFrame(FrameFlags.None, (int)error, streamId);
-                await server.WriteFrameAsync(resetStream);
+                await connection.WriteFrameAsync(resetStream);
 
                 await AssertProtocolErrorAsync(sendTask, error);
 
                 // Send a frame on the now-closed stream.
                 DataFrame invalidFrame = new DataFrame(new byte[10], FrameFlags.None, 0, streamId);
-                await server.WriteFrameAsync(invalidFrame);
+                await connection.WriteFrameAsync(invalidFrame);
 
                 if (!IsWinHttpHandler)
                 {
                     // The client should close the connection as this is a fatal connection level error.
-                    Assert.Null(await server.ReadFrameAsync(TimeSpan.FromSeconds(30)));
+                    Assert.Null(await connection.ReadFrameAsync(TimeSpan.FromSeconds(30)));
                 }
             }
         }
 
-        private static async Task<int> EstablishConnectionAndProcessOneRequestAsync(HttpClient client, Http2LoopbackServer server)
+        private static async Task<(int, Http2LoopbackConnection)> EstablishConnectionAndProcessOneRequestAsync(HttpClient client, Http2LoopbackServer server)
         {
             int streamId = -1;
 
             // Establish connection and send initial request/response to ensure connection is available for subsequent use
+            Http2LoopbackConnection connection = null;
+
             await new[]
             {
                 Task.Run(async () =>
@@ -711,13 +744,13 @@ namespace System.Net.Http.Functional.Tests
                 }),
                 Task.Run(async () =>
                 {
-                    await server.EstablishConnectionAsync();
-                    streamId = await server.ReadRequestHeaderAsync();
-                    await server.SendDefaultResponseAsync(streamId);
+                    connection = await server.EstablishConnectionAsync();
+                    streamId = await connection.ReadRequestHeaderAsync();
+                    await connection.SendDefaultResponseAsync(streamId);
                 })
             }.WhenAllOrAnyFailed(TestHelper.PassingTestTimeoutMilliseconds);
 
-            return streamId;
+            return (streamId, connection);
         }
 
         [ConditionalFact(nameof(SupportsAlpn))]
@@ -727,14 +760,14 @@ namespace System.Net.Http.Functional.Tests
             using (var server = Http2LoopbackServer.CreateServer())
             using (HttpClient client = CreateHttpClient())
             {
-                int streamId = await EstablishConnectionAndProcessOneRequestAsync(client, server);
+                (int streamId, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server);
 
                 // Send GOAWAY.
                 GoAwayFrame goAwayFrame = new GoAwayFrame(streamId, 0, new byte[0], 0);
-                await server.WriteFrameAsync(goAwayFrame);
+                await connection.WriteFrameAsync(goAwayFrame);
 
                 // The client should close the connection.
-                await server.WaitForConnectionShutdownAsync();
+                await connection.WaitForConnectionShutdownAsync();
 
                 // New request should cause a new connection
                 await EstablishConnectionAndProcessOneRequestAsync(client, server);
@@ -747,7 +780,7 @@ namespace System.Net.Http.Functional.Tests
             using (var server = Http2LoopbackServer.CreateServer())
             using (HttpClient client = CreateHttpClient())
             {
-                await EstablishConnectionAndProcessOneRequestAsync(client, server);
+                (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server);
 
                 // Issue three requests
                 Task<HttpResponseMessage> sendTask1 = client.GetAsync(server.Address);
@@ -755,9 +788,9 @@ namespace System.Net.Http.Functional.Tests
                 Task<HttpResponseMessage> sendTask3 = client.GetAsync(server.Address);
 
                 // Receive three requests
-                int streamId1 = await server.ReadRequestHeaderAsync();
-                int streamId2 = await server.ReadRequestHeaderAsync();
-                int streamId3 = await server.ReadRequestHeaderAsync();
+                int streamId1 = await connection.ReadRequestHeaderAsync();
+                int streamId2 = await connection.ReadRequestHeaderAsync();
+                int streamId3 = await connection.ReadRequestHeaderAsync();
 
                 Assert.True(streamId1 < streamId2);
                 Assert.True(streamId2 < streamId3);
@@ -767,24 +800,24 @@ namespace System.Net.Http.Functional.Tests
                 // First response: Don't send anything yet
 
                 // Second response: Send headers, no body yet
-                await server.SendDefaultResponseHeadersAsync(streamId2);
+                await connection.SendDefaultResponseHeadersAsync(streamId2);
 
                 // Third response: Send headers, partial body
-                await server.SendDefaultResponseHeadersAsync(streamId3);
-                await server.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
+                await connection.SendDefaultResponseHeadersAsync(streamId3);
+                await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
 
                 // Send a GOAWAY frame that indicates that we will process all three streams
                 GoAwayFrame goAwayFrame = new GoAwayFrame(streamId3, 0, new byte[0], 0);
-                await server.WriteFrameAsync(goAwayFrame);
+                await connection.WriteFrameAsync(goAwayFrame);
 
                 // Finish sending responses
-                await server.SendDefaultResponseHeadersAsync(streamId1);
-                await server.SendResponseDataAsync(streamId1, new byte[10], endStream: true);
-                await server.SendResponseDataAsync(streamId2, new byte[10], endStream: true);
-                await server.SendResponseDataAsync(streamId3, new byte[5], endStream: true);
+                await connection.SendDefaultResponseHeadersAsync(streamId1);
+                await connection.SendResponseDataAsync(streamId1, new byte[10], endStream: true);
+                await connection.SendResponseDataAsync(streamId2, new byte[10], endStream: true);
+                await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: true);
 
                 // We will not send any more frames, so send EOF now, and ensure the client handles this properly.
-                server.ShutdownSend();
+                connection.ShutdownSend();
 
                 // Receive all responses
                 HttpResponseMessage response1 = await sendTask1;
@@ -798,7 +831,7 @@ namespace System.Net.Http.Functional.Tests
                 Assert.Equal(10, (await response3.Content.ReadAsByteArrayAsync()).Length);
 
                 // Now that all pending responses have been sent, the client should close the connection.
-                await server.WaitForConnectionShutdownAsync();
+                await connection.WaitForConnectionShutdownAsync();
 
                 // New request should cause a new connection
                 await EstablishConnectionAndProcessOneRequestAsync(client, server);
@@ -812,7 +845,7 @@ namespace System.Net.Http.Functional.Tests
             using (Http2LoopbackServer server = Http2LoopbackServer.CreateServer())
             using (HttpClient client = CreateHttpClient())
             {
-                await EstablishConnectionAndProcessOneRequestAsync(client, server);
+                (_, Http2LoopbackConnection connection) = await EstablishConnectionAndProcessOneRequestAsync(client, server);
 
                 // Issue three requests
                 Task<HttpResponseMessage> sendTask1 = client.GetAsync(server.Address);
@@ -820,9 +853,9 @@ namespace System.Net.Http.Functional.Tests
                 Task<HttpResponseMessage> sendTask3 = client.GetAsync(server.Address);
 
                 // Receive three requests
-                int streamId1 = await server.ReadRequestHeaderAsync();
-                int streamId2 = await server.ReadRequestHeaderAsync();
-                int streamId3 = await server.ReadRequestHeaderAsync();
+                int streamId1 = await connection.ReadRequestHeaderAsync();
+                int streamId2 = await connection.ReadRequestHeaderAsync();
+                int streamId3 = await connection.ReadRequestHeaderAsync();
 
                 Assert.InRange(streamId1, int.MinValue, streamId2 - 1);
                 Assert.InRange(streamId2, int.MinValue, streamId3 - 1);
@@ -832,37 +865,37 @@ namespace System.Net.Http.Functional.Tests
                 // First response: Don't send anything yet
 
                 // Second response: Send headers, no body yet
-                await server.SendDefaultResponseHeadersAsync(streamId2);
+                await connection.SendDefaultResponseHeadersAsync(streamId2);
 
                 // Third response: Send headers, partial body
-                await server.SendDefaultResponseHeadersAsync(streamId3);
-                await server.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
+                await connection.SendDefaultResponseHeadersAsync(streamId3);
+                await connection.SendResponseDataAsync(streamId3, new byte[5], endStream: false);
 
                 // Send a GOAWAY frame that indicates that we will abort all the requests.
                 var goAwayFrame = new GoAwayFrame(0, (int)ProtocolErrors.ENHANCE_YOUR_CALM, new byte[0], 0);
-                await server.WriteFrameAsync(goAwayFrame);
+                await connection.WriteFrameAsync(goAwayFrame);
 
                 // We will not send any more frames, so send EOF now, and ensure the client handles this properly.
-                server.ShutdownSend();
+                connection.ShutdownSend();
 
                 await AssertProtocolErrorAsync(sendTask1, ProtocolErrors.ENHANCE_YOUR_CALM);
                 await AssertProtocolErrorAsync(sendTask2, ProtocolErrors.ENHANCE_YOUR_CALM);
                 await AssertProtocolErrorAsync(sendTask3, ProtocolErrors.ENHANCE_YOUR_CALM);
 
                 // Now that all pending responses have been sent, the client should close the connection.
-                await server.WaitForConnectionShutdownAsync();
+                await connection.WaitForConnectionShutdownAsync();
 
                 // New request should cause a new connection
                 await EstablishConnectionAndProcessOneRequestAsync(client, server);
             }
         }
 
-        private static async Task<int> ReadToEndOfStream(Http2LoopbackServer server, int streamId)
+        private static async Task<int> ReadToEndOfStream(Http2LoopbackConnection connection, int streamId)
         {
             int bytesReceived = 0;
             while (true)
             {
-                Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
 
                 Assert.Equal(streamId, frame.StreamId);
                 Assert.Equal(FrameType.Data, frame.Type);
@@ -895,9 +928,9 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 int streamId = frame.StreamId;
                 Assert.Equal(FrameType.Headers, frame.Type);
                 Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
@@ -906,7 +939,7 @@ namespace System.Net.Http.Functional.Tests
                 int bytesReceived = 0;
                 while (bytesReceived < InitialWindowSize)
                 {
-                    frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                    frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                     Assert.Equal(streamId, frame.StreamId);
                     Assert.Equal(FrameType.Data, frame.Type);
                     Assert.Equal(FrameFlags.None, frame.Flags);
@@ -918,52 +951,52 @@ namespace System.Net.Http.Functional.Tests
                 Assert.Equal(InitialWindowSize, bytesReceived);
 
                 // Issue another read. It shouldn't complete yet. Wait a brief period of time to ensure it doesn't complete.
-                Task<Frame> readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Task<Frame> readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase connection window by one. This should still not complete the read.
-                await server.WriteFrameAsync(new WindowUpdateFrame(1, 0));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(1, 0));
 
                 await Task.Delay(500);
 
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase stream window by two. This should complete the read with a single byte.
-                await server.WriteFrameAsync(new WindowUpdateFrame(2, streamId));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(2, streamId));
 
                 frame = await readFrameTask;
                 Assert.Equal(1, frame.Length);
                 bytesReceived++;
 
                 // Issue another read and ensure it doesn't complete yet.
-                readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase connection window by two. This should complete the read with a single byte.
-                await server.WriteFrameAsync(new WindowUpdateFrame(2, 0));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(2, 0));
 
                 frame = await readFrameTask;
                 Assert.Equal(1, frame.Length);
                 bytesReceived++;
 
                 // Issue another read and ensure it doesn't complete yet.
-                readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase connection window to allow exactly the remaining request size. This should still not complete the read.
-                await server.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived - 1, 0));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived - 1, 0));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase stream window to allow exactly the remaining request size. This should allow the rest of the request to be sent.
-                await server.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived, streamId));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(ContentSize - bytesReceived, streamId));
 
                 frame = await readFrameTask;
                 Assert.Equal(streamId, frame.StreamId);
@@ -974,11 +1007,11 @@ namespace System.Net.Http.Functional.Tests
                 bytesReceived += frame.Length;
 
                 // Read to end of stream
-                bytesReceived += await ReadToEndOfStream(server, streamId);
+                bytesReceived += await ReadToEndOfStream(connection, streamId);
 
                 Assert.Equal(ContentSize, bytesReceived);
 
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await clientTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -999,12 +1032,12 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
                 // Bump connection window so it won't block the client.
-                await server.WriteFrameAsync(new WindowUpdateFrame(ContentSize - DefaultInitialWindowSize, 0));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(ContentSize - DefaultInitialWindowSize, 0));
 
-                Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 int streamId = frame.StreamId;
                 Assert.Equal(FrameType.Headers, frame.Type);
                 Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
@@ -1013,7 +1046,7 @@ namespace System.Net.Http.Functional.Tests
                 int bytesReceived = 0;
                 while (bytesReceived < DefaultInitialWindowSize)
                 {
-                    frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                    frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                     Assert.Equal(streamId, frame.StreamId);
                     Assert.Equal(FrameType.Data, frame.Type);
                     Assert.Equal(FrameFlags.None, frame.Flags);
@@ -1025,39 +1058,39 @@ namespace System.Net.Http.Functional.Tests
                 Assert.Equal(DefaultInitialWindowSize, bytesReceived);
 
                 // Issue another read. It shouldn't complete yet. Wait a brief period of time to ensure it doesn't complete.
-                Task<Frame> readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Task<Frame> readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Change SETTINGS_INITIAL_WINDOW_SIZE to 0. This will make the client's credit go negative.
-                server.ExpectSettingsAck();
-                await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 0 }));
+                connection.ExpectSettingsAck();
+                await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 0 }));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase stream window by one. Client credit will still be negative.
-                await server.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Change SETTINGS_INITIAL_WINDOW_SIZE to 1. Client credit will still be negative.
-                server.ExpectSettingsAck();
-                await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 1 }));
+                connection.ExpectSettingsAck();
+                await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 1 }));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase stream window so client credit will be 0.
-                await server.WriteFrameAsync(new WindowUpdateFrame(DefaultInitialWindowSize - 2, streamId));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(DefaultInitialWindowSize - 2, streamId));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase stream window by one, so client can now send a single byte.
-                await server.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(1, streamId));
 
                 frame = await readFrameTask;
                 Assert.Equal(FrameType.Data, frame.Type);
@@ -1065,14 +1098,14 @@ namespace System.Net.Http.Functional.Tests
                 bytesReceived++;
 
                 // Issue another read and ensure it doesn't complete yet.
-                readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase SETTINGS_INITIAL_WINDOW_SIZE to 2, so client can now send a single byte.
-                server.ExpectSettingsAck();
-                await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 2 }));
+                connection.ExpectSettingsAck();
+                await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = 2 }));
 
                 frame = await readFrameTask;
                 Assert.Equal(FrameType.Data, frame.Type);
@@ -1080,14 +1113,14 @@ namespace System.Net.Http.Functional.Tests
                 bytesReceived++;
 
                 // Issue another read and ensure it doesn't complete yet.
-                readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
 
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Increase SETTINGS_INITIAL_WINDOW_SIZE to be enough that the client can send the rest of the content.
-                server.ExpectSettingsAck();
-                await server.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = ContentSize - (DefaultInitialWindowSize - 1) }));
+                connection.ExpectSettingsAck();
+                await connection.WriteFrameAsync(new SettingsFrame(new SettingsEntry { SettingId = SettingId.InitialWindowSize, Value = ContentSize - (DefaultInitialWindowSize - 1) }));
 
                 frame = await readFrameTask;
                 Assert.Equal(streamId, frame.StreamId);
@@ -1098,11 +1131,11 @@ namespace System.Net.Http.Functional.Tests
                 bytesReceived += frame.Length;
 
                 // Read to end of stream
-                bytesReceived += await ReadToEndOfStream(server, streamId);
+                bytesReceived += await ReadToEndOfStream(connection, streamId);
 
                 Assert.Equal(ContentSize, bytesReceived);
 
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await clientTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -1118,12 +1151,12 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                server.IgnoreWindowUpdates();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                connection.IgnoreWindowUpdates();
 
                 // Process first request and send response.
-                int streamId = await server.ReadRequestHeaderAsync();
-                await server.SendDefaultResponseAsync(streamId);
+                int streamId = await connection.ReadRequestHeaderAsync();
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -1131,8 +1164,8 @@ namespace System.Net.Http.Functional.Tests
                 // Change MaxConcurrentStreams setting and wait for ack.
                 // (We don't want to send any new requests until we receive the ack, otherwise we may have a timing issue.)
                 SettingsFrame settingsFrame = new SettingsFrame(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 0 });
-                await server.WriteFrameAsync(settingsFrame);
-                Frame settingsAckFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                await connection.WriteFrameAsync(settingsFrame);
+                Frame settingsAckFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 Assert.Equal(FrameType.Settings, settingsAckFrame.Type);
                 Assert.Equal(FrameFlags.Ack, settingsAckFrame.Flags);
 
@@ -1141,14 +1174,14 @@ namespace System.Net.Http.Functional.Tests
                 Task<HttpResponseMessage> sendTask2 = client.GetAsync(server.Address);
 
                 // Issue another read. It shouldn't complete yet. Wait a brief period of time to ensure it doesn't complete.
-                Task<Frame> readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Task<Frame> readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Change MaxConcurrentStreams again to allow a single request to come through.
-                server.ExpectSettingsAck();
+                connection.ExpectSettingsAck();
                 settingsFrame = new SettingsFrame(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 1 });
-                await server.WriteFrameAsync(settingsFrame);
+                await connection.WriteFrameAsync(settingsFrame);
 
                 // First request should be sent
                 Frame frame = await readFrameTask;
@@ -1156,12 +1189,12 @@ namespace System.Net.Http.Functional.Tests
                 streamId = frame.StreamId;
 
                 // Issue another read. Second request should not be sent yet.
-                readFrameTask = server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                readFrameTask = connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 await Task.Delay(500);
                 Assert.False(readFrameTask.IsCompleted);
 
                 // Send response for first request
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
                 response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
 
@@ -1171,7 +1204,7 @@ namespace System.Net.Http.Functional.Tests
                 streamId = frame.StreamId;
 
                 // Send response for second request
-                await server.SendDefaultResponseAsync(streamId);
+                await connection.SendDefaultResponseAsync(streamId);
                 response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
             }
@@ -1186,12 +1219,12 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                server.IgnoreWindowUpdates();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                connection.IgnoreWindowUpdates();
 
                 // Process first request and send response.
-                int streamId = await server.ReadRequestHeaderAsync();
-                await server.SendDefaultResponseAsync(streamId);
+                int streamId = await connection.ReadRequestHeaderAsync();
+                await connection.SendDefaultResponseAsync(streamId);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -1199,8 +1232,8 @@ namespace System.Net.Http.Functional.Tests
                 // Change MaxConcurrentStreams setting and wait for ack.
                 // (We don't want to send any new requests until we receive the ack, otherwise we may have a timing issue.)
                 SettingsFrame settingsFrame = new SettingsFrame(new SettingsEntry { SettingId = SettingId.MaxConcurrentStreams, Value = 0 });
-                await server.WriteFrameAsync(settingsFrame);
-                Frame settingsAckFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                await connection.WriteFrameAsync(settingsFrame);
+                Frame settingsAckFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 Assert.Equal(FrameType.Settings, settingsAckFrame.Type);
                 Assert.Equal(FrameFlags.Ack, settingsAckFrame.Flags);
 
@@ -1247,9 +1280,9 @@ namespace System.Net.Http.Functional.Tests
                 var cts = new CancellationTokenSource();
                 Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content, cts.Token);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 int streamId = frame.StreamId;
                 Assert.Equal(FrameType.Headers, frame.Type);
                 Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
@@ -1258,7 +1291,7 @@ namespace System.Net.Http.Functional.Tests
                 int bytesReceived = 0;
                 while (bytesReceived < InitialWindowSize)
                 {
-                    frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                    frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                     Assert.Equal(streamId, frame.StreamId);
                     Assert.Equal(FrameType.Data, frame.Type);
                     Assert.Equal(FrameFlags.None, frame.Flags);
@@ -1279,7 +1312,7 @@ namespace System.Net.Http.Functional.Tests
                 Assert.True(stopwatch.ElapsedMilliseconds < 30000);
 
                 // The server should receive a RstStream frame.
-                frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 Assert.Equal(FrameType.RstStream, frame.Type);
             }
         }
@@ -1302,16 +1335,16 @@ namespace System.Net.Http.Functional.Tests
 
                 Task<HttpResponseMessage> clientTask = client.PostAsync(server.Address, content, cts.Token);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                Frame frame = await server.ReadFrameAsync(TimeSpan.FromSeconds(30));
+                Frame frame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(30));
                 int streamId = frame.StreamId;
                 Assert.Equal(FrameType.Headers, frame.Type);
                 Assert.Equal(FrameFlags.EndHeaders, frame.Flags);
 
                 // Increase the size of the HTTP/2 Window, so that it is large enough to fill the
                 // TCP window when we do not perform any reads on the server side.
-                await server.WriteFrameAsync(new WindowUpdateFrame(InitialWindowSize, streamId));
+                await connection.WriteFrameAsync(new WindowUpdateFrame(InitialWindowSize, streamId));
 
                 // Give the client time to read the window update frame, and for the write to pend.
                 await Task.Delay(1000);
@@ -1342,20 +1375,20 @@ namespace System.Net.Http.Functional.Tests
             },
             async server =>
             {
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync(readBody : false);
+                (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody : false);
                 Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
 
                 if (send100Continue)
                 {
-                    await server.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Continue);
+                    await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Continue);
                 }
-                await server.ReadBodyAsync();
-                await server.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK);
-                await server.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("OK"));
-                await server.SendGoAway(streamId);
-                await server.WaitForConnectionShutdownAsync();
+                await connection.ReadBodyAsync();
+                await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.OK);
+                await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("OK"));
+                await connection.SendGoAway(streamId);
+                await connection.WaitForConnectionShutdownAsync();
             });
         }
 
@@ -1383,25 +1416,25 @@ namespace System.Net.Http.Functional.Tests
             },
             async server =>
             {
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync(readBody : false);
+                (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody : false);
                 Assert.Equal("100-continue", requestData.GetSingleHeaderValue("Expect"));
 
                 // Wait for client so start sending body.
                 await tsc.Task.ConfigureAwait(false);
                 // And reject content with 403.
-                await server.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Forbidden);
-                await server.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("no no!"));
+                await connection.SendResponseHeadersAsync(streamId, endStream: false, HttpStatusCode.Forbidden);
+                await connection.SendResponseBodyAsync(streamId, Encoding.ASCII.GetBytes("no no!"));
                 try
                 {
                     // Client should send reset.
-                    await server.ReadBodyAsync();
+                    await connection.ReadBodyAsync();
                     Assert.True(false, "Should not be here");
                 }
                 catch (IOException) { };
-                await server.SendGoAway(streamId);
-                await server.WaitForConnectionShutdownAsync();
+                await connection.SendGoAway(streamId);
+                await connection.WaitForConnectionShutdownAsync();
             });
         }
 
@@ -1434,9 +1467,9 @@ namespace System.Net.Http.Functional.Tests
             },
             async server =>
             {
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                (int streamId, HttpRequestData requestData) = await server.ReadAndParseRequestHeaderAsync(readBody : false);
+                (int streamId, HttpRequestData requestData) = await connection.ReadAndParseRequestHeaderAsync(readBody : false);
 
                 // Wait for client so start sending body.
                 await tsc.Task.ConfigureAwait(false);
@@ -1444,25 +1477,25 @@ namespace System.Net.Http.Functional.Tests
                 if (shouldWait)
                 {
                     // Read body first before sending back response
-                    await server.ReadBodyAsync();
+                    await connection.ReadBodyAsync();
                 }
 
-                await server.SendResponseHeadersAsync(streamId, endStream: false, responseCode);
-                await server.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes(responseContent), endStream: false);
+                await connection.SendResponseHeadersAsync(streamId, endStream: false, responseCode);
+                await connection.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes(responseContent), endStream: false);
                 if (!shouldWait)
                 {
                     try
                     {
                         // Client should send reset.
-                        await server.ReadBodyAsync();
+                        await connection.ReadBodyAsync();
                         if (responseCode != HttpStatusCode.OK) Assert.True(false, "Should not be here");
                     }
                     catch (IOException) when (responseCode != HttpStatusCode.OK) { };
                 }
                 var headers = new HttpHeaderData[] { new HttpHeaderData("x-last", "done") };
-                await server.SendResponseHeadersAsync(streamId, endStream: true, isTrailingHeader : true, headers: headers);
-                await server.SendGoAway(streamId);
-                await server.WaitForConnectionShutdownAsync();
+                await connection.SendResponseHeadersAsync(streamId, endStream: true, isTrailingHeader : true, headers: headers);
+                await connection.SendGoAway(streamId);
+                await connection.WaitForConnectionShutdownAsync();
             });
         }
 
@@ -1519,9 +1552,9 @@ namespace System.Net.Http.Functional.Tests
                 IList<HttpHeaderData> headers = new HttpHeaderData[] { new HttpHeaderData(":status", "300"), new HttpHeaderData("x-test", "Http2GetAsync_MultipleStatusHeaders_Throws") };
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
-                await server.SendResponseHeadersAsync(streamId, endStream : true, headers: headers);
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
+                await connection.SendResponseHeadersAsync(streamId, endStream : true, headers: headers);
                 await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
             }
         }
@@ -1536,9 +1569,9 @@ namespace System.Net.Http.Functional.Tests
                 IList<HttpHeaderData> headers = new HttpHeaderData[] { new HttpHeaderData("x-test", "Http2GetAsync_StatusHeaderNotFirst_Throws"), new HttpHeaderData(":status", "200") };
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
-                await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
+                await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
 
                 await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
             }
@@ -1554,11 +1587,11 @@ namespace System.Net.Http.Functional.Tests
                 IList<HttpHeaderData> headers = new HttpHeaderData[] { new HttpHeaderData(":path", "http"), new HttpHeaderData("x-test", "Http2GetAsync_TrailigPseudo_Throw") };
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
-                int streamId = await server.ReadRequestHeaderAsync();
-                await server.SendDefaultResponseHeadersAsync(streamId);
-                await server.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes("hello"), endStream: false);
-                await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
+                await connection.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendResponseDataAsync(streamId, Encoding.ASCII.GetBytes("hello"), endStream: false);
+                await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader : true, headers: headers);
 
                 await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
             }
@@ -1582,7 +1615,7 @@ namespace System.Net.Http.Functional.Tests
                 {
                     try
                     {
-                        SettingsFrame clientSettings = await server.EstablishConnectionAsync();
+                        (Http2LoopbackConnection connection, SettingsFrame clientSettings) = await server.EstablishConnectionGetSettingsAsync();
 
                         SettingsEntry clientWindowSizeSetting = clientSettings.Entries.SingleOrDefault(x => x.SettingId == SettingId.InitialWindowSize);
                         int clientWindowSize = clientWindowSizeSetting.SettingId == SettingId.InitialWindowSize ? (int)clientWindowSizeSetting.Value : 65535;
@@ -1590,10 +1623,10 @@ namespace System.Net.Http.Functional.Tests
                         // Exceed the window size by 1 byte.
                         ++clientWindowSize; 
 
-                        int streamId = await server.ReadRequestHeaderAsync();
+                        int streamId = await connection.ReadRequestHeaderAsync();
 
                         // Write the response.
-                        await server.SendDefaultResponseHeadersAsync(streamId);
+                        await connection.SendDefaultResponseHeadersAsync(streamId);
 
                         byte[] buffer = new byte[4096];
                         int totalSent = 0;
@@ -1603,7 +1636,7 @@ namespace System.Net.Http.Functional.Tests
                             int sendSize = Math.Min(buffer.Length, clientWindowSize - totalSent);
                             ReadOnlyMemory<byte> sendBuf = buffer.AsMemory(0, sendSize);
 
-                            await server.SendResponseDataAsync(streamId, sendBuf, endStream: false);
+                            await connection.SendResponseDataAsync(streamId, sendBuf, endStream: false);
                             totalSent += sendSize;
                         }
 
@@ -1611,7 +1644,7 @@ namespace System.Net.Http.Functional.Tests
                         // If client is misbehaving, we'll get an OperationCanceledException due to timeout.
                         try
                         {
-                            Frame clientFrame = await server.ReadFrameAsync(TimeSpan.FromSeconds(5));
+                            Frame clientFrame = await connection.ReadFrameAsync(TimeSpan.FromSeconds(5));
                             Assert.True(clientFrame == null || (clientFrame.Type == FrameType.RstStream && clientFrame.StreamId == streamId),
                                 "Unexpected frame received from HttpClient; Expected either RST_STREAM or connection reset.");
                         }
index a231cf2..20e2563 100644 (file)
@@ -769,15 +769,15 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Response header.
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
 
                 // Response data.
-                await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes, endStream: true));
+                await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes, endStream: true));
 
                 // Server doesn't send trailing header frame.
                 HttpResponseMessage response = await sendTask;
@@ -795,18 +795,18 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Response header.
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
 
                 // Response data, missing Trailers.
-                await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+                await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
 
                 // Additional trailing header frame.
-                await server.SendResponseHeadersAsync(streamId, isTrailingHeader:true, headers: TrailingHeaders, endStream : true);
+                await connection.SendResponseHeadersAsync(streamId, isTrailingHeader:true, headers: TrailingHeaders, endStream : true);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -824,15 +824,15 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Response header.
-                await server.SendDefaultResponseHeadersAsync(streamId);
-                await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+                await connection.SendDefaultResponseHeadersAsync(streamId);
+                await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
                 // Additional trailing header frame with pseudo-headers again..
-                await server.SendResponseHeadersAsync(streamId, isTrailingHeader:false, headers: TrailingHeaders, endStream : true);
+                await connection.SendResponseHeadersAsync(streamId, isTrailingHeader:false, headers: TrailingHeaders, endStream : true);
 
                 await Assert.ThrowsAsync<HttpRequestException>(() => sendTask);
             }
@@ -846,15 +846,15 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address, HttpCompletionOption.ResponseHeadersRead);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Response header.
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
 
                 // Response data, missing Trailers.
-                await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+                await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -870,8 +870,8 @@ namespace System.Net.Http.Functional.Tests
                 Assert.Empty(response.TrailingHeaders);
 
                 // Finish data stream and write out trailing headers.
-                await server.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
-                await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
+                await connection.WriteFrameAsync(MakeDataFrame(streamId, DataBytes));
+                await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
 
                 // Read data until EOF is reached
                 while (stream.Read(data, 0, data.Length) != 0);
@@ -890,13 +890,13 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Response header.
-                await server.SendDefaultResponseHeadersAsync(streamId);
-                await server.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendResponseHeadersAsync(streamId, endStream : true, isTrailingHeader:true, headers: TrailingHeaders);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -914,17 +914,17 @@ namespace System.Net.Http.Functional.Tests
             {
                 Task<HttpResponseMessage> sendTask = client.GetAsync(server.Address);
 
-                await server.EstablishConnectionAsync();
+                Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
 
-                int streamId = await server.ReadRequestHeaderAsync();
+                int streamId = await connection.ReadRequestHeaderAsync();
 
                 // Response header.
-                await server.SendDefaultResponseHeadersAsync(streamId);
+                await connection.SendDefaultResponseHeadersAsync(streamId);
 
                 // No data.
 
                 // Response trailing headers
-                await server.SendResponseHeadersAsync(streamId, isTrailingHeader: true, headers: TrailingHeaders);
+                await connection.SendResponseHeadersAsync(streamId, isTrailingHeader: true, headers: TrailingHeaders);
 
                 HttpResponseMessage response = await sendTask;
                 Assert.Equal(HttpStatusCode.OK, response.StatusCode);
@@ -1606,21 +1606,21 @@ namespace System.Net.Http.Functional.Tests
                     SetDefaultRequestVersion(client, HttpVersion.Version20);
                     Task<string> request1 = client.GetStringAsync(url);
 
-                    await server.EstablishConnectionAsync();
-                    int streamId = await server.ReadRequestHeaderAsync();
-                    await server.SendDefaultResponseAsync(streamId);
+                    Http2LoopbackConnection connection = await server.EstablishConnectionAsync();
+                    int streamId = await connection.ReadRequestHeaderAsync();
+                    await connection.SendDefaultResponseAsync(streamId);
                     await request1;
 
                     // Wait a small amount of time before making the second request, to give the first request time to timeout.
                     await Task.Delay(100);
                     // Grab reference to underlying socket and stream to make sure they are not disposed and closed.
-                    (Socket socket, Stream stream) = server.ResetNetwork();
+                    (Socket socket, Stream stream) = connection.ResetNetwork();
 
                     // Make second request and expect it to be served from a different connection.
                     Task<string> request2 = client.GetStringAsync(url);
-                    await server.EstablishConnectionAsync();
-                    streamId = await server.ReadRequestHeaderAsync();
-                    await server.SendDefaultResponseAsync(streamId);
+                    connection = await server.EstablishConnectionAsync();
+                    streamId = await connection.ReadRequestHeaderAsync();
+                    await connection.SendDefaultResponseAsync(streamId);
                     await request2;
 
                     // Close underlying socket from first connection.
index 61600e8..609c200 100644 (file)
     <Compile Include="$(CommonTestPath)\System\Net\Http\Http2LoopbackServer.cs">
       <Link>Common\System\Net\Http\Http2LoopbackServer.cs</Link>
     </Compile>
+    <Compile Include="$(CommonTestPath)\System\Net\Http\Http2LoopbackConnection.cs">
+      <Link>Common\System\Net\Http\Http2LoopbackConnection.cs</Link>
+    </Compile>
     <Compile Include="$(CommonTestPath)\System\Net\Http\HuffmanDecoder.cs">
       <Link>Common\System\Net\Http\HuffmanDecoder.cs</Link>
     </Compile>