public async Task ReceivingHeadersWithPaddingAndPriorityShouldBeSupported( int?numPadding, bool hasPrio) { 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(); var outBuf = new byte[Settings.Default.MaxFrameSize]; for (var i = 0; i < nrStreams; i++) { headersOk = false; streamIdOk = false; streamStateOk = false; var headerOffset = 0; if (numPadding != null) { outBuf[0] = (byte)numPadding; headerOffset += 1; } if (hasPrio) { // TODO: Initialize the priority data in this test properly // if priority data checking gets inserted later on headerOffset += 5; } var result = hEncoder.EncodeInto( new ArraySegment <byte>(outBuf, headerOffset, outBuf.Length - headerOffset), DefaultGetHeaders); var totalLength = headerOffset + result.UsedBytes; if (numPadding != null) { totalLength += numPadding.Value; } var flags = (byte)HeadersFrameFlags.EndOfHeaders; if (numPadding != null) { flags |= (byte)HeadersFrameFlags.Padded; } if (hasPrio) { flags |= (byte)HeadersFrameFlags.Priority; } var fh = new FrameHeader { Type = FrameType.Headers, Length = totalLength, Flags = (byte)flags, StreamId = streamId, }; await inPipe.WriteFrameHeader(fh); await inPipe.WriteAsync(new ArraySegment <byte>(outBuf, 0, totalLength)); 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 static async Task <Result> CreateConnectionAndStream( StreamState state, ILoggerProvider loggerProvider, IBufferedPipe iPipe, IBufferedPipe oPipe, Settings?localSettings = null, Settings?remoteSettings = null, HuffmanStrategy huffmanStrategy = HuffmanStrategy.Never) { if (state == StreamState.Idle) { throw new Exception("Not supported"); } var hEncoder = new Encoder(); var conn = await ConnectionUtils.BuildEstablishedConnection( false, iPipe, oPipe, loggerProvider, null, localSettings : localSettings, remoteSettings : remoteSettings, huffmanStrategy : huffmanStrategy); var endOfStream = false; if (state == StreamState.HalfClosedLocal || state == StreamState.Closed) { endOfStream = true; } var stream = await conn.CreateStreamAsync( DefaultGetHeaders, endOfStream : endOfStream); await oPipe.ReadAndDiscardHeaders(1u, endOfStream); if (state == StreamState.HalfClosedRemote || state == StreamState.Closed) { var outBuf = new byte[Settings.Default.MaxFrameSize]; var result = hEncoder.EncodeInto( new ArraySegment <byte>(outBuf), DefaultStatusHeaders); await iPipe.WriteFrameHeaderWithTimeout( new FrameHeader { Type = FrameType.Headers, Flags = (byte)(HeadersFrameFlags.EndOfHeaders | HeadersFrameFlags.EndOfStream), StreamId = 1u, Length = result.UsedBytes, }); await iPipe.WriteAsync(new ArraySegment <byte>(outBuf, 0, result.UsedBytes)); var readHeadersTask = stream.ReadHeadersAsync(); var combined = await Task.WhenAny(readHeadersTask, Task.Delay( ReadableStreamTestExtensions.ReadTimeout)); Assert.True(readHeadersTask == combined, "Expected to receive headers"); var headers = await readHeadersTask; Assert.True(headers.SequenceEqual(DefaultStatusHeaders)); // Consume the data - which should be empty var data = await stream.ReadAllToArrayWithTimeout(); Assert.Equal(0, data.Length); } else if (state == StreamState.Reset) { await iPipe.WriteResetStream(1u, ErrorCode.Cancel); await Assert.ThrowsAsync <StreamResetException>( () => stream.ReadHeadersAsync()); } return(new Result { hEncoder = hEncoder, conn = conn, stream = stream, }); }
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 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 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 IncompleteHeaderBlocksShouldBeDetected( int nrContinuations) { 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(); // Construct a proper set of header here, where a single headerfield could // by splitted over more than 1 fragment var totalHeaders = DefaultGetHeaders.TakeWhile( h => h.Name.StartsWith(":")); totalHeaders = totalHeaders.Append( new HeaderField { Name = "longname", Value = "longvalue" }); totalHeaders = totalHeaders.Append( new HeaderField { Name = "abc", Value = "xyz" }); // Encode all headers in a single header block var hBuf = new byte[32 * 1024]; var encodeRes = hEncoder.EncodeInto(new ArraySegment <byte>(hBuf), totalHeaders); Assert.Equal(totalHeaders.Count(), encodeRes.FieldCount); Assert.True( encodeRes.UsedBytes >= 5, "Test must encode headers with at least 5 bytes to work properly"); // Write header data in multiple parts // The last part will miss one byte var offset = 0; var length = encodeRes.UsedBytes; var isLast = nrContinuations == 0; var dataLength = isLast ? (length - 1) : 3; var fh = new FrameHeader { Type = FrameType.Headers, Length = dataLength, StreamId = 1u, Flags = (byte)(isLast ? HeadersFrameFlags.EndOfHeaders : 0), }; await inPipe.WriteFrameHeader(fh); await inPipe.WriteAsync(new ArraySegment <byte>(hBuf, offset, dataLength)); offset += dataLength; length -= dataLength; for (var i = 0; i < nrContinuations; i++) { isLast = i == nrContinuations - 1; dataLength = isLast ? (length - 1) : 1; fh = new FrameHeader { Type = FrameType.Continuation, Length = dataLength, StreamId = 1u, Flags = (byte)(isLast ? ContinuationFrameFlags.EndOfHeaders : 0), }; await inPipe.WriteFrameHeader(fh); await inPipe.WriteAsync(new ArraySegment <byte>(hBuf, offset, dataLength)); offset += dataLength; length -= dataLength; } Assert.True(1 == length, "Expected to send all but 1 byte"); // Expect a GoAway as reaction to incomplete headers await outPipe.AssertGoAwayReception(ErrorCode.CompressionError, 0u); await outPipe.AssertStreamEnd(); }
public async Task ContinuationsWherePartsDontContainFullHeaderMustBeSupported( int nrContinuations) { 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(); // Construct a proper set of header here, where a single headerfield could // by splitted over more than 1 fragment var totalHeaders = DefaultGetHeaders.TakeWhile( h => h.Name.StartsWith(":")); totalHeaders = totalHeaders.Append( new HeaderField { Name = "longname", Value = "longvalue" }); totalHeaders = totalHeaders.Append( new HeaderField { Name = "abc", Value = "xyz" }); // Encode all headers in a single header block var hBuf = new byte[32 * 1024]; var encodeRes = hEncoder.EncodeInto(new ArraySegment <byte>(hBuf), totalHeaders); Assert.Equal(totalHeaders.Count(), encodeRes.FieldCount); Assert.True( encodeRes.UsedBytes >= 5, "Test must encode headers with at least 5 bytes to work properly"); // Write the first 3 bytes in a header frame // These cover all pseudoheaders, which wouldn't be affected by // possible bugs anyway var fh = new FrameHeader { Type = FrameType.Headers, Length = 3, StreamId = 1u, Flags = (byte)0, }; await inPipe.WriteFrameHeader(fh); await inPipe.WriteAsync(new ArraySegment <byte>(hBuf, 0, 3)); var offset = 3; var length = encodeRes.UsedBytes - 3; for (var i = 0; i < nrContinuations; i++) { var isLast = i == nrContinuations - 1; var dataLength = isLast ? length : 1; fh = new FrameHeader { Type = FrameType.Continuation, Length = dataLength, StreamId = 1u, Flags = (byte)(isLast ? ContinuationFrameFlags.EndOfHeaders : 0), }; await inPipe.WriteFrameHeader(fh); await inPipe.WriteAsync(new ArraySegment <byte>(hBuf, offset, dataLength)); offset += dataLength; length -= dataLength; } 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 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 static async Task<Result> CreateConnectionAndStream( StreamState state, ILoggerProvider loggerProvider, IBufferedPipe iPipe, IBufferedPipe oPipe, Settings? localSettings = null, Settings? remoteSettings = null, HuffmanStrategy huffmanStrategy = HuffmanStrategy.Never) { IStream stream = null; var handlerDone = new SemaphoreSlim(0); if (state == StreamState.Idle) { throw new Exception("Not supported"); } Func<IStream, bool> listener = (s) => { Task.Run(async () => { stream = s; try { await s.ReadHeadersAsync(); if (state == StreamState.Reset) { s.Cancel(); return; } if (state == StreamState.HalfClosedRemote || state == StreamState.Closed) { await s.ReadAllToArrayWithTimeout(); } if (state == StreamState.HalfClosedLocal || state == StreamState.Closed) { await s.WriteHeadersAsync( DefaultStatusHeaders, true); } } finally { handlerDone.Release(); } }); return true; }; var conn = await ConnectionUtils.BuildEstablishedConnection( true, iPipe, oPipe, loggerProvider, listener, localSettings: localSettings, remoteSettings: remoteSettings, huffmanStrategy: huffmanStrategy); var hEncoder = new Encoder(); await iPipe.WriteHeaders( hEncoder, 1, false, DefaultGetHeaders); if (state == StreamState.HalfClosedRemote || state == StreamState.Closed) { await iPipe.WriteData(1u, 0, endOfStream: true); } var ok = await handlerDone.WaitAsync( ReadableStreamTestExtensions.ReadTimeout); if (!ok) throw new Exception("Stream handler did not finish"); if (state == StreamState.HalfClosedLocal || state == StreamState.Closed) { // Consume the sent headers and data await oPipe.ReadAndDiscardHeaders(1u, true); } else if (state == StreamState.Reset) { // Consume the sent reset frame await oPipe.AssertResetStreamReception(1, ErrorCode.Cancel); } return new Result { conn = conn, stream = stream, hEncoder = hEncoder, }; }