public async Task ReceivingAnInformationalHeaderAfterANormalHeaderShouldBeAnError() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var res = await StreamCreator.CreateConnectionAndStream( StreamState.Open, loggerProvider, inPipe, outPipe); await inPipe.WriteHeaders(res.hEncoder, 1u, false, DefaultStatusHeaders); // Expect to receive the status headers var recvdHeaders = await res.stream.ReadHeadersAsync(); Assert.True(DefaultStatusHeaders.SequenceEqual(recvdHeaders)); // Send informational headers to the client var infoHeaders = new HeaderField[] { new HeaderField { Name = ":status", Value = "100" }, new HeaderField { Name = "extension-field", Value = "bar" }, }; await inPipe.WriteHeaders(res.hEncoder, 1u, false, infoHeaders); // Expect to receive an error await outPipe.AssertResetStreamReception(1u, ErrorCode.ProtocolError); Assert.Equal(StreamState.Reset, res.stream.State); await Assert.ThrowsAsync <StreamResetException>( () => res.stream.ReadHeadersAsync()); }
public async Task ReceivingHeadersOrDataOnAClosedStreamShouldTriggerAStreamReset( StreamState streamState, bool sendHeaders, bool sendData, bool sendTrailers) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var localCloseDone = new SemaphoreSlim(0); var res = await StreamCreator.CreateConnectionAndStream( streamState, loggerProvider, inPipe, outPipe); if (sendHeaders) { await inPipe.WriteHeaders(res.hEncoder, 1, false, DefaultGetHeaders); } if (sendData) { await inPipe.WriteData(1u, 0); } if (sendTrailers) { await inPipe.WriteHeaders(res.hEncoder, 1, true, DefaultGetHeaders); } await outPipe.AssertResetStreamReception(1u, ErrorCode.StreamClosed); var expectedState = streamState == StreamState.Closed ? StreamState.Closed : StreamState.Reset; Assert.Equal(expectedState, res.stream.State); }
public async Task SendingInvalidTrailersShouldTriggerAStreamReset() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var headers = new HeaderField[] { new HeaderField { Name = ":method", Value = "GET" }, new HeaderField { Name = ":scheme", Value = "http" }, new HeaderField { Name = ":path", Value = "/" }, }; var trailers = new HeaderField[] { new HeaderField { Name = ":method", Value = "GET" }, }; Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, false, headers); await inPipe.WriteHeaders(hEncoder, 1, true, trailers); await outPipe.AssertResetStreamReception(1, ErrorCode.ProtocolError); }
public async Task ClientsShouldBeAbleToReceiveInformationalHeaders() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var res = await StreamCreator.CreateConnectionAndStream( StreamState.Open, loggerProvider, inPipe, outPipe); // Send and receive first set of informational headers var readInfoHeaders1Task = res.stream.ReadHeadersAsync(); Assert.False(readInfoHeaders1Task.IsCompleted); var infoHeaders1 = new HeaderField[] { new HeaderField { Name = ":status", Value = "100" }, new HeaderField { Name = "extension-field", Value = "bar" }, }; await inPipe.WriteHeaders(res.hEncoder, 1u, false, infoHeaders1); var recvdInfoHeaders1 = await readInfoHeaders1Task; Assert.True(infoHeaders1.SequenceEqual(recvdInfoHeaders1)); // Send and receive second set of informational headers var readInfoHeaders2Task = res.stream.ReadHeadersAsync(); Assert.False(readInfoHeaders2Task.IsCompleted); var infoHeaders2 = new HeaderField[] { new HeaderField { Name = ":status", Value = "108" }, new HeaderField { Name = "extension-field-b", Value = "bar2" }, }; await inPipe.WriteHeaders(res.hEncoder, 1u, false, infoHeaders2); var recvdInfoHeaders2 = await readInfoHeaders2Task; Assert.True(infoHeaders2.SequenceEqual(recvdInfoHeaders2)); // Send and receive final headers var recvHeadersTask = res.stream.ReadHeadersAsync(); Assert.False(recvHeadersTask.IsCompleted); await inPipe.WriteHeaders(res.hEncoder, 1u, true, DefaultStatusHeaders); var recvdHeaders = await recvHeadersTask; Assert.True(DefaultStatusHeaders.SequenceEqual(recvdHeaders)); }
public async Task ADataFrameOnAnUnknownStreamIdShouldTriggerAStreamReset( bool isServer, uint streamId) { // TODO: Add test cases for clients var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( isServer, inPipe, outPipe, loggerProvider, listener); // Establish a high stream ID, which means all below are invalid var hEncoder = new Encoder(); var createdStreamId = 111u; if (!isServer) { throw new Exception("For clients the stream must be created from connection"); } await inPipe.WriteHeaders( hEncoder, createdStreamId, false, DefaultGetHeaders); await inPipe.WriteData(streamId, 0); await outPipe.AssertResetStreamReception(streamId, ErrorCode.StreamClosed); }
public async Task ReceivingHeadersWithEndOfStreamShouldAllowToReadEndOfStream() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var handlerDone = new SemaphoreSlim(0); var gotEos = false; var streamStateOk = false; Func<IStream, bool> listener = (s) => { Task.Run(async () => { await s.ReadHeadersAsync(); var buf = new byte[1024]; var res = await s.ReadAsync(new ArraySegment<byte>(buf)); gotEos = res.EndOfStream && res.BytesRead == 0; streamStateOk = s.State == StreamState.HalfClosedRemote; handlerDone.Release(); }); return true; }; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, true, DefaultGetHeaders); var requestDone = await handlerDone.WaitAsync(ReadableStreamTestExtensions.ReadTimeout); Assert.True(requestDone, "Expected handler to complete within timeout"); Assert.True(gotEos); Assert.True(streamStateOk); }
public async Task ConnectionShouldIgnoreResetsforUnknownStreams() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var conn = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); var streamId = 7u; await inPipe.WriteHeaders( hEncoder, streamId, false, TestHeaders.DefaultGetHeaders); await inPipe.WriteResetStream(streamId - 2, ErrorCode.RefusedStream); await inPipe.WriteResetStream(streamId - 4, ErrorCode.Cancel); // Send a ping afterwards // If we get a response the reset frame in between was ignored await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); }
public async Task IfSettingsDecreaseHeaderTableNextOutgoingHeadersShouldContainAnUpdate() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => { Task.Run(() => { var res = new HeaderField[] { new HeaderField { Name = ":status", Value = "200" }, }; s.WriteHeadersAsync(res, false); }); return(true); }; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); // Change remote settings 2 times var settings = Settings.Default; settings.HeaderTableSize = 8; await inPipe.WriteSettings(settings); await outPipe.AssertSettingsAck(); settings.HeaderTableSize = 30; await inPipe.WriteSettings(settings); await outPipe.AssertSettingsAck(); // Establish a stream // When we send a response through it we should observe the size udpate var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, false, DefaultGetHeaders); // Wait for the incoming status headers with header update var fh = await outPipe.ReadFrameHeaderWithTimeout(); Assert.Equal(FrameType.Headers, fh.Type); Assert.Equal((byte)(HeadersFrameFlags.EndOfHeaders), fh.Flags); Assert.Equal(1u, fh.StreamId); Assert.Equal(3, fh.Length); var data = new byte[fh.Length]; await outPipe.ReadAllWithTimeout(new ArraySegment <byte>(data)); Assert.Equal(0x28, data[0]); // Size Update to 8 Assert.Equal(0x3e, data[1]); // Size Update to 30 Assert.Equal(0x88, data[2]); // :status 200 }
public async Task ReceivingHeadersWithATooSmallStreamIdShouldTriggerAStreamReset( bool noListener) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func<IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 33, false, DefaultGetHeaders); await inPipe.WriteHeaders(hEncoder, 31, false, DefaultGetHeaders); // Remark: The specification actually wants a connection error / GOAWAY // to be emitted. However as the implementation can not safely determine // if that stream ID was never used and valid we send and check for // a stream reset. await outPipe.AssertResetStreamReception(31, ErrorCode.StreamClosed); }
public async Task HeadersWithContentLengthShouldForceDataLengthValidation( int contentLen, int[] dataLength, bool useTrailers, bool shouldError) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var headers = TestHeaders.DefaultGetHeaders.Append( new HeaderField { Name = "content-length", Value = contentLen.ToString() }); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, false, headers); for (var i = 0; i < dataLength.Length; i++) { var isEos = i == (dataLength.Length - 1) && !useTrailers; await inPipe.WriteData(1u, dataLength[i], endOfStream : isEos); } if (useTrailers) { await inPipe.WriteHeaders(hEncoder, 1, true, new HeaderField[0]); } if (shouldError) { await outPipe.AssertResetStreamReception(1u, ErrorCode.ProtocolError); } else { await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); } }
public async Task ClientUpgradeRequestShouldYieldStream1() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var upgrade = new ClientUpgradeRequestBuilder().Build(); var config = new ConnectionConfigurationBuilder(false) .Build(); var conn = new Connection( config, inPipe, outPipe, new Connection.Options { Logger = loggerProvider.CreateLogger("http2Con"), ClientUpgradeRequest = upgrade, }); await conn.PerformHandshakes(inPipe, outPipe); var stream = await upgrade.UpgradeRequestStream; Assert.Equal(1u, stream.Id); Assert.Equal(1, conn.ActiveStreamCount); Assert.Equal(StreamState.HalfClosedLocal, stream.State); var readHeadersTask = stream.ReadHeadersAsync(); Assert.False(readHeadersTask.IsCompleted); var hEncoder = new Http2.Hpack.Encoder(); await inPipe.WriteHeaders(hEncoder, 1u, false, DefaultStatusHeaders); Assert.True( await Task.WhenAny( readHeadersTask, Task.Delay(ReadableStreamTestExtensions.ReadTimeout)) == readHeadersTask, "Expected to read headers, got timeout"); var headers = await readHeadersTask; Assert.True(headers.SequenceEqual(DefaultStatusHeaders)); Assert.Equal(StreamState.HalfClosedLocal, stream.State); await inPipe.WriteData(1u, 100, 5, true); var data = await stream.ReadAllToArrayWithTimeout(); Assert.True(data.Length == 100); Assert.Equal(StreamState.Closed, stream.State); Assert.Equal(0, conn.ActiveStreamCount); }
public async Task ReceivingHeadersOrDataOnAResetStreamShouldProduceAClosedStreamError( bool sendHeaders, bool sendData, bool sendTrailers) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var r = await StreamCreator.CreateConnectionAndStream( StreamState.Reset, loggerProvider, inPipe, outPipe); if (sendHeaders) { await inPipe.WriteHeaders(r.hEncoder, 1, false, DefaultGetHeaders); } if (sendData) { await inPipe.WriteData(1u, 0); } if (sendTrailers) { await inPipe.WriteHeaders(r.hEncoder, 1, true, DefaultGetHeaders); } await outPipe.AssertResetStreamReception(1, ErrorCode.StreamClosed); }
public async Task HeadersOnStreamIdWhichCanNotBeRemoteInitiatedShouldTriggerAStreamReset( bool isServer, uint streamId) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( isServer, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, streamId, false, DefaultGetHeaders); await outPipe.AssertResetStreamReception(streamId, ErrorCode.StreamClosed); }
public async Task ReceivingHeadersWithNoAttachedListenerOrRefusingListenerShouldTriggerStreamReset( bool noListener) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func<IStream, bool> listener = null; if (!noListener) listener = (s) => false; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, false, DefaultGetHeaders); await outPipe.AssertResetStreamReception(1, ErrorCode.RefusedStream); }
public async Task HeadersOnStreamId0ShouldTriggerAGoAway( bool isServer) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( isServer, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 0, false, DefaultGetHeaders); await outPipe.AssertGoAwayReception(ErrorCode.ProtocolError, 0); await outPipe.AssertStreamEnd(); }
public async Task ShouldResetStreamWhenStreamFlowControlWindowIsOverloaded( int amount) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, false, DefaultGetHeaders); await inPipe.WriteWindowUpdate(1, amount); await outPipe.AssertResetStreamReception(1, ErrorCode.FlowControlError); }
public async Task ReceivingHeadersShouldCreateNewStreamsAndAllowToReadTheHeaders() { const int nrStreams = 10; var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); int nrAcceptedStreams = 0; var handlerDone = new SemaphoreSlim(0); uint streamId = 1; var headersOk = false; var streamIdOk = false; var streamStateOk = false; Func<IStream, bool> listener = (s) => { Interlocked.Increment(ref nrAcceptedStreams); Task.Run(async () => { var rcvdHeaders = await s.ReadHeadersAsync(); headersOk = DefaultGetHeaders.SequenceEqual(rcvdHeaders); streamIdOk = s.Id == streamId; streamStateOk = s.State == StreamState.Open; handlerDone.Release(); }); return true; }; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); for (var i = 0; i < nrStreams; i++) { headersOk = false; streamIdOk = false; streamStateOk = false; await inPipe.WriteHeaders(hEncoder, streamId, false, DefaultGetHeaders); var requestDone = await handlerDone.WaitAsync(ReadableStreamTestExtensions.ReadTimeout); Assert.True(requestDone, "Expected handler to complete within timeout"); Assert.True(headersOk); Assert.True(streamIdOk); Assert.True(streamStateOk); streamId += 2; } Assert.Equal(nrStreams, nrAcceptedStreams); }
public async Task ReceivingHeaders2TimesShouldTriggerAStreamReset( bool isServer, bool headersAreEndOfStream) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); // Establish open streams, which means headers are sent in both // directions var res = await StreamCreator.CreateConnectionAndStream( isServer, loggerProvider, inPipe, outPipe); // Write a second header await inPipe.WriteHeaders( res.hEncoder, 1, headersAreEndOfStream, DefaultGetHeaders); await outPipe.AssertResetStreamReception(1, ErrorCode.ProtocolError); Assert.Equal(StreamState.Reset, res.stream.State); }
public async Task EmptyDataFramesShouldBeValidSeperatorsBeforeTrailers( bool isServer) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var res = await StreamCreator.CreateConnectionAndStream( isServer, loggerProvider, inPipe, outPipe); // Send a 0byte data frame await inPipe.WriteData(1u, 0); // Send trailers await inPipe.WriteHeaders(res.hEncoder, 1u, true, DefaultTrailingHeaders); // Check for no stream error await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); }
public async Task ShouldAllowToSetTheMaxPossibleStreamFlowControlWindowSize() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, false, DefaultGetHeaders); var amount = int.MaxValue - 65535; await inPipe.WriteWindowUpdate(1, amount); // Check aliveness with ping/pong await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); }
public async Task NewStreamsAfterGoAwayShouldBeRejected() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var res = await ServerStreamTests.StreamCreator.CreateConnectionAndStream( StreamState.Open, loggerProvider, inPipe, outPipe); // Start the GoAway process await res.conn.GoAwayAsync(ErrorCode.NoError, false); // Expect the GoAway message await outPipe.AssertGoAwayReception(ErrorCode.NoError, 1u); // Try to establish a new stream var hEncoder = new Http2.Hpack.Encoder(); await inPipe.WriteHeaders(hEncoder, 3, true, TestHeaders.DefaultGetHeaders); // Expect a stream rejection await outPipe.AssertResetStreamReception(3u, ErrorCode.RefusedStream); }
public async Task ReceivingTrailersShouldUnblockDataReceptionAndPresentThem( bool isServer) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var res = await StreamCreator.CreateConnectionAndStream( isServer, loggerProvider, inPipe, outPipe); var readDataTask = res.stream.ReadAllToArrayWithTimeout(); var fh = new FrameHeader { Type = FrameType.Data, Length = 4, StreamId = 1, Flags = (byte)0, }; await inPipe.WriteFrameHeader(fh); await inPipe.WriteAsync( new ArraySegment <byte>( System.Text.Encoding.ASCII.GetBytes("ABCD"))); var trailers = new HeaderField[] { new HeaderField { Name = "trai", Value = "ler" }, }; await inPipe.WriteHeaders(res.hEncoder, 1, true, trailers); var bytes = await readDataTask; Assert.Equal(4, bytes.Length); Assert.Equal("ABCD", System.Text.Encoding.ASCII.GetString(bytes)); Assert.Equal(StreamState.HalfClosedRemote, res.stream.State); var rcvdTrailers = await res.stream.ReadTrailersAsync(); Assert.Equal(trailers, rcvdTrailers); }
public async Task InvalidContinuationFramesShouldLeadToGoAway( uint contStreamId, int?contLength, FrameType contFrameType) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); // Send a valid HEADERS frame await inPipe.WriteHeaders( hEncoder, 1, false, DefaultGetHeaders.Take(2), false); // Followed by an invalid continuation frame var outBuf = new byte[Settings.Default.MaxFrameSize]; var result = hEncoder.EncodeInto( new ArraySegment <byte>(outBuf), DefaultGetHeaders.Skip(2)); var length = contLength ?? result.UsedBytes; var fh = new FrameHeader { Type = contFrameType, StreamId = contStreamId, Length = length, Flags = 0, }; await inPipe.WriteFrameHeader(fh); await outPipe.AssertGoAwayReception(ErrorCode.ProtocolError, 0u); await outPipe.AssertStreamEnd(); }
public async Task IncomingStreamsAfterMaxConcurrentStreamsShouldBeRejected( int maxConcurrentStreams) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var acceptedStreams = new List<IStream>(); Func<IStream, bool> listener = (s) => { lock (acceptedStreams) { acceptedStreams.Add(s); } return true; }; var settings = Settings.Default; settings.MaxConcurrentStreams = (uint)maxConcurrentStreams; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener, localSettings: settings); var hEncoder = new Encoder(); // Open maxConcurrentStreams var streamId = 1u; for (var i = 0; i < maxConcurrentStreams; i++) { await inPipe.WriteHeaders(hEncoder, streamId, false, DefaultGetHeaders); streamId += 2; } // Assert no rejection and response so far await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); lock (acceptedStreams) { Assert.Equal(maxConcurrentStreams, acceptedStreams.Count); } // Try to open an additional stream await inPipe.WriteHeaders(hEncoder, streamId, false, DefaultGetHeaders); // This one should be rejected await outPipe.AssertResetStreamReception(streamId, ErrorCode.RefusedStream); lock (acceptedStreams) { Assert.Equal(maxConcurrentStreams, acceptedStreams.Count); } // Once a stream is closed a new one should be acceptable await inPipe.WriteResetStream(streamId-2, ErrorCode.Cancel); streamId += 2; await inPipe.WriteHeaders(hEncoder, streamId, false, DefaultGetHeaders); // Assert no error response await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); lock (acceptedStreams) { // +1 because the dead stream isn't removed Assert.Equal(maxConcurrentStreams+1, acceptedStreams.Count); // Check if the reset worked Assert.Equal( StreamState.Reset, acceptedStreams[acceptedStreams.Count-2].State); } }
public async Task MaxHeaderListSizeViolationsShouldBeDetected( uint maxHeaderListSize, int headersInFirstFrame, int headersInContFrame, bool shouldError) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); Func <IStream, bool> listener = (s) => true; var settings = Settings.Default; settings.MaxHeaderListSize = maxHeaderListSize; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener, localSettings : settings, huffmanStrategy : HuffmanStrategy.Never); var hEncoder = new Encoder(); var headers = new List <HeaderField>(); // Add the default headers // These take 123 bytes in store and 3 bytes in transmission headers.AddRange(new HeaderField[] { new HeaderField { Name = ":method", Value = "GET" }, new HeaderField { Name = ":path", Value = "/" }, new HeaderField { Name = ":scheme", Value = "http" }, }); var currentHeadersLength = headers .Select(hf => hf.Name.Length + hf.Value.Length + 32) .Sum(); // Create a header which takes 34 bytes in store and 5 bytes in transmission var extraHeader = new HeaderField { Name = "a", Value = "b", Sensitive = true }; while (currentHeadersLength < headersInFirstFrame) { headers.Add(extraHeader); currentHeadersLength += 1 + 1 + 32; } await inPipe.WriteHeaders( hEncoder, 1, false, headers, headersInContFrame == 0); if (headersInContFrame != 0) { headers.Clear(); currentHeadersLength = 0; while (currentHeadersLength < headersInContFrame) { headers.Add(extraHeader); currentHeadersLength += 1 + 1 + 32; } await inPipe.WriteContinuation( hEncoder, 1, headers, true); } if (shouldError) { // TODO: The spec says actually the remote should answer with // an HTTP431 error - but that happens on another layer await outPipe.AssertGoAwayReception(ErrorCode.ProtocolError, 0u); await outPipe.AssertStreamEnd(); } else { await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); } }
public async Task ContinuationFrameHeadersShouldBeAddedToTotalHeaders( int[] nrHeadersInFrame) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); IStream stream = null; IEnumerable <HeaderField> receivedHeaders = null; SemaphoreSlim handlerDone = new SemaphoreSlim(0); Func <IStream, bool> listener = (s) => { stream = s; Task.Run(async() => { receivedHeaders = await s.ReadHeadersAsync(); handlerDone.Release(); }); return(true); }; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener); var hEncoder = new Encoder(); var totalHeaders = DefaultGetHeaders; var isContinuation = false; var toSkip = 0; var isEndOfHeaders = false; for (var frameNr = 0; frameNr <= nrHeadersInFrame.Length; frameNr++) { var headersToSend = totalHeaders.Skip(toSkip); if (frameNr != nrHeadersInFrame.Length) { var toSend = nrHeadersInFrame[frameNr]; headersToSend = headersToSend.Take(toSend); toSkip += toSend; } else { // send remaining headers isEndOfHeaders = true; } if (!isContinuation) { await inPipe.WriteHeaders( hEncoder, 1, false, headersToSend, isEndOfHeaders); isContinuation = true; } else { await inPipe.WriteContinuation( hEncoder, 1, headersToSend, isEndOfHeaders); } } var handlerCompleted = await handlerDone.WaitAsync( ReadableStreamTestExtensions.ReadTimeout); Assert.True(handlerCompleted, "Expected stream handler to complete"); Assert.True( totalHeaders.SequenceEqual(receivedHeaders), "Expected to receive all sent headers"); }
public async Task TheNextOutgoingStreamAfterUpgradeShouldUseId3() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var upgrade = new ClientUpgradeRequestBuilder().Build(); var config = new ConnectionConfigurationBuilder(false) .Build(); var conn = new Connection( config, inPipe, outPipe, new Connection.Options { Logger = loggerProvider.CreateLogger("http2Con"), ClientUpgradeRequest = upgrade, }); await conn.PerformHandshakes(inPipe, outPipe); var stream = await upgrade.UpgradeRequestStream; Assert.Equal(1u, stream.Id); var readHeadersTask = stream.ReadHeadersAsync(); Assert.False(readHeadersTask.IsCompleted); var nextStream = await conn.CreateStreamAsync(DefaultGetHeaders); await outPipe.ReadAndDiscardHeaders(3u, false); Assert.Equal(3u, nextStream.Id); Assert.True(stream != nextStream); Assert.Equal(StreamState.HalfClosedLocal, stream.State); Assert.Equal(StreamState.Open, nextStream.State); var hEncoder = new Http2.Hpack.Encoder(); await inPipe.WriteHeaders(hEncoder, 3u, true, DefaultStatusHeaders); var nextStreamHeaders = await nextStream.ReadHeadersAsync(); Assert.True(nextStreamHeaders.SequenceEqual(DefaultStatusHeaders)); Assert.False(readHeadersTask.IsCompleted); Assert.Equal(StreamState.HalfClosedRemote, nextStream.State); Assert.Equal(StreamState.HalfClosedLocal, stream.State); Assert.Equal(2, conn.ActiveStreamCount); await nextStream.WriteAsync(new ArraySegment <byte>(new byte[0]), true); await outPipe.ReadAndDiscardData(3u, true, 0); Assert.Equal(StreamState.Closed, nextStream.State); Assert.Equal(1, conn.ActiveStreamCount); var headers2 = DefaultStatusHeaders.Append( new HeaderField() { Name = "hh", Value = "vv" }); await inPipe.WriteHeaders(hEncoder, 1u, false, headers2); var streamHeaders = await readHeadersTask; Assert.True(streamHeaders.SequenceEqual(headers2)); await inPipe.WriteData(1u, 10, 0, true); var data = await stream.ReadAllToArrayWithTimeout(); Assert.True(data.Length == 10); Assert.Equal(StreamState.Closed, stream.State); Assert.Equal(0, conn.ActiveStreamCount); }
public async Task TheRemoteHeaderTableSizeShouldOnlyBeUsedUpToConfiguredLimit() { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); var handlerDone = new SemaphoreSlim(0); Func <IStream, bool> listener = (s) => { Task.Run(() => { var res = new HeaderField[] { new HeaderField { Name = ":status", Value = "200" }, }; s.WriteHeadersAsync(res, false); }); handlerDone.Release(); return(true); }; // Lower the initial window size so that stream window updates are // sent earlier than connection window updates var localSettings = Settings.Default; localSettings.HeaderTableSize = 20000; var http2Con = await ConnectionUtils.BuildEstablishedConnection( true, inPipe, outPipe, loggerProvider, listener, localSettings : localSettings); // Change remote settings, which grants us a giant header table var settings = Settings.Default; settings.HeaderTableSize = 50 * 1024 * 1024; await inPipe.WriteSettings(settings); await outPipe.AssertSettingsAck(); // Establish a stream // When we send a response through it we should observe the size udpate var hEncoder = new Encoder(); await inPipe.WriteHeaders(hEncoder, 1, false, DefaultGetHeaders); var ok = await handlerDone.WaitAsync( ReadableStreamTestExtensions.ReadTimeout); if (!ok) { throw new Exception("Stream was not created"); } // Wait for the incoming status headers with header update var fh = await outPipe.ReadFrameHeaderWithTimeout(); Assert.Equal(FrameType.Headers, fh.Type); Assert.Equal((byte)(HeadersFrameFlags.EndOfHeaders), fh.Flags); Assert.Equal(1u, fh.StreamId); // Observe headers with status update // We should update the header table to 20000 Assert.Equal(5, fh.Length); var data = new byte[fh.Length]; await outPipe.ReadAllWithTimeout(new ArraySegment <byte>(data)); Assert.Equal(0x3f, data[0]); // Size Update to 20000 => 20000 - 31 = 19969 Assert.Equal(0x81, data[1]); // 19969 % 128 + 128 = 0x81, 19969 / 128 = 156 Assert.Equal(0x9c, data[2]); // 156 % 128 + 128 = 0x9c, 156 / 128 = 1 Assert.Equal(0x01, data[3]); // 1 Assert.Equal(0x88, data[4]); // :status 200 }
public async Task ViolationsOfTheConnectionFlowControlWindowShouldBeDetected( int?padLen, uint streamId, int dataAmount, bool isError) { var inPipe = new BufferedPipe(1024); var outPipe = new BufferedPipe(1024); // Use a bigger flow control window for streams so that the connection // window errors and streams dont send window updates. // Also update maxFrameSize, otherwise the connection will send // window updates faster than we can violate the contract var settings = Settings.Default; settings.InitialWindowSize = 256 * 1024; settings.MaxFrameSize = 1024 * 1024; var res = await ServerStreamTests.StreamCreator.CreateConnectionAndStream( StreamState.Open, loggerProvider, inPipe, outPipe, localSettings : settings); // Open an additional stream, so that streamId 3 is not in the IDLE // range, which causes a connection error await inPipe.WriteHeaders( res.hEncoder, 555u, false, DefaultGetHeaders); // Try to send the data // This might fail, if the connection goes away before // everything is read try { await inPipe.WriteData(streamId, dataAmount, padLen : padLen); } catch (Exception e) { if (!isError || !(e is TimeoutException)) { throw; } } if (isError) { await outPipe.AssertGoAwayReception(ErrorCode.FlowControlError, 555u); await outPipe.AssertStreamEnd(); await res.conn.Done; Assert.Equal(StreamState.Reset, res.stream.State); } else { // Expect the connection to be alive await outPipe.AssertWindowUpdate(0u, 65535); if (streamId != 1) { // We expect a reset for the unknown stream on which data // was transmitted await outPipe.AssertResetStreamReception(streamId, ErrorCode.StreamClosed); } await inPipe.WritePing(new byte[8], false); await outPipe.ReadAndDiscardPong(); Assert.Equal(StreamState.Open, res.stream.State); } }