public void CanSendGoAwayUsingVoidPromise()
        {
            _handler = NewHandler();
            IByteBuffer data      = DummyData();
            var         errorCode = Http2Error.InternalError;

            _handler = NewHandler();
            var cause = new Http2RuntimeException("fake exception");

            _frameWriter
            .Setup(x => x.WriteGoAwayAsync(
                       It.IsAny <IChannelHandlerContext>(),
                       It.IsAny <int>(),
                       It.IsAny <Http2Error>(),
                       It.IsAny <IByteBuffer>(),
                       It.IsAny <IPromise>()
                       ))
            .Returns <IChannelHandlerContext, int, Http2Error, IByteBuffer, IPromise>((c, id, err, buf, p) =>
            {
                Assert.False(p.IsVoid);
                // This is what DefaultHttp2FrameWriter does... I hate mocking :-(.
                var aggregatedPromise = new SimplePromiseAggregator(p);
                aggregatedPromise.NewPromise();
                aggregatedPromise.DoneAllocatingPromises();
                aggregatedPromise.SetException(cause);
                return(aggregatedPromise.Task);
            });
            _handler.GoAwayAsync(_ctx.Object, STREAM_ID, errorCode, data, Http2TestUtil.NewVoidPromise(_channel.Object));
            _pipeline.Verify(x => x.FireExceptionCaught(It.Is <Exception>(v => ReferenceEquals(v, cause))));
        }
        private static void WriteHeaders(IChannelHandlerContext ctx, IHttp2ConnectionEncoder encoder, int streamId,
                                         HttpHeaders headers, IHttp2Headers http2Headers, bool endStream,
                                         SimplePromiseAggregator promiseAggregator)
        {
            int dependencyId = headers.GetInt(
                HttpConversionUtil.ExtensionHeaderNames.StreamDependencyId, 0);
            short weight = headers.GetShort(
                HttpConversionUtil.ExtensionHeaderNames.StreamWeight, Http2CodecUtil.DefaultPriorityWeight);

            _ = encoder.WriteHeadersAsync(ctx, streamId, http2Headers, dependencyId, weight, false,
                                          0, endStream, promiseAggregator.NewPromise());
        }
Example #3
0
        public virtual Task WriteGoAwayAsync(IChannelHandlerContext ctx, int lastStreamId,
                                             Http2Error errorCode, IByteBuffer debugData, IPromise promise)
        {
            SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise);

            try
            {
                if (lastStreamId < 0)
                {
                    ThrowHelper.ThrowArgumentException_PositiveOrZero(ExceptionArgument.LastStreamId);
                }
                VerifyErrorCode((long)errorCode);

                int         payloadLength = 8 + debugData.ReadableBytes;
                IByteBuffer buf           = ctx.Allocator.Buffer(Http2CodecUtil.GoAwayFrameHeaderLength);
                // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
                // in the catch block.
                Http2CodecUtil.WriteFrameHeaderInternal(buf, payloadLength, Http2FrameTypes.GoAway, new Http2Flags(), 0);
                _ = buf.WriteInt(lastStreamId);
                _ = buf.WriteInt((int)errorCode);
                _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise());
            }
            catch (Exception t)
            {
                try
                {
                    _ = debugData.Release();
                }
                finally
                {
                    promiseAggregator.SetException(t);
                    _ = promiseAggregator.DoneAllocatingPromises();
                }

                return(promiseAggregator.Task);
            }

            try
            {
                _ = ctx.WriteAsync(debugData, promiseAggregator.NewPromise());
            }
            catch (Exception t)
            {
                promiseAggregator.SetException(t);
            }

            _ = promiseAggregator.DoneAllocatingPromises();
            return(promiseAggregator.Task);
        }
Example #4
0
        public virtual Task WriteFrameAsync(IChannelHandlerContext ctx, Http2FrameTypes frameType,
                                            int streamId, Http2Flags flags, IByteBuffer payload, IPromise promise)
        {
            SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise);

            try
            {
                if (streamId < 0)
                {
                    ThrowHelper.ThrowArgumentException_PositiveOrZero(ExceptionArgument.StreamID);
                }
                IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength);
                // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership
                // in the catch block.
                Http2CodecUtil.WriteFrameHeaderInternal(buf, payload.ReadableBytes, frameType, flags, streamId);
                _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise());
            }
            catch (Exception t)
            {
                try
                {
                    _ = payload.Release();
                }
                finally
                {
                    promiseAggregator.SetException(t);
                    _ = promiseAggregator.DoneAllocatingPromises();
                }

                return(promiseAggregator.Task);
            }

            try
            {
                _ = ctx.WriteAsync(payload, promiseAggregator.NewPromise());
            }
            catch (Exception t)
            {
                promiseAggregator.SetException(t);
            }

            _ = promiseAggregator.DoneAllocatingPromises();
            return(promiseAggregator.Task);
        }
Example #5
0
        /// <summary>
        /// Writes as many continuation frames as needed until <paramref name="headerBlock"/> are consumed.
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="streamId"></param>
        /// <param name="headerBlock"></param>
        /// <param name="promiseAggregator"></param>
        /// <returns></returns>
        Task WriteContinuationFramesAsync(IChannelHandlerContext ctx, int streamId,
                                          IByteBuffer headerBlock, SimplePromiseAggregator promiseAggregator)
        {
            Http2Flags flags = new Http2Flags();

            if (headerBlock.IsReadable())
            {
                // The frame header (and padding) only changes on the last frame, so allocate it once and re-use
                int         fragmentReadableBytes = Math.Min(headerBlock.ReadableBytes, _maxFrameSize);
                IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.ContinuationFrameHeaderLength);
                Http2CodecUtil.WriteFrameHeaderInternal(buf, fragmentReadableBytes, Http2FrameTypes.Continuation, flags, streamId);

                do
                {
                    fragmentReadableBytes = Math.Min(headerBlock.ReadableBytes, _maxFrameSize);
                    IByteBuffer fragment = headerBlock.ReadRetainedSlice(fragmentReadableBytes);

                    if (headerBlock.IsReadable())
                    {
                        _ = ctx.WriteAsync(buf.Retain(), promiseAggregator.NewPromise());
                    }
                    else
                    {
                        // The frame header is different for the last frame, so re-allocate and release the old buffer
                        flags = flags.EndOfHeaders(true);
                        _     = buf.Release();
                        buf   = ctx.Allocator.Buffer(Http2CodecUtil.ContinuationFrameHeaderLength);
                        Http2CodecUtil.WriteFrameHeaderInternal(buf, fragmentReadableBytes, Http2FrameTypes.Continuation, flags, streamId);
                        _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise());
                    }

                    _ = ctx.WriteAsync(fragment, promiseAggregator.NewPromise());
                }while (headerBlock.IsReadable());
            }

            return(promiseAggregator.Task);
        }
        public virtual Task WriteSettingsAckAsync(IChannelHandlerContext ctx, IPromise promise)
        {
            if (_outstandingRemoteSettingsQueue is null)
            {
                return(_frameWriter.WriteSettingsAckAsync(ctx, promise));
            }
            Http2Settings settings = _outstandingRemoteSettingsQueue.RemoveFirst();

            if (settings is null)
            {
                _ = promise.TrySetException(ThrowHelper.GetConnectionError_attempted_to_write_a_SETTINGS_ACK_with_no_pending_SETTINGS());
                return(promise.Task);
            }
            SimplePromiseAggregator aggregator = new SimplePromiseAggregator(promise); // , ctx.channel(), ctx.executor()

            // Acknowledge receipt of the settings. We should do this before we process the settings to ensure our
            // remote peer applies these settings before any subsequent frames that we may send which depend upon
            // these new settings. See https://github.com/netty/netty/issues/6520.
            _ = _frameWriter.WriteSettingsAckAsync(ctx, aggregator.NewPromise());

            // We create a "new promise" to make sure that status from both the write and the application are taken into
            // account independently.
            var applySettingsPromise = aggregator.NewPromise();

            try
            {
                RemoteSettings(settings);
                applySettingsPromise.Complete();
            }
            catch (Exception e)
            {
                applySettingsPromise.SetException(e);
                _lifecycleManager.OnError(ctx, true, e);
            }
            return(aggregator.DoneAllocatingPromises().Task);
        }
        /// <summary>
        /// Handles conversion of <see cref="IHttpMessage"/> and <see cref="IHttpContent"/> to HTTP/2 frames.
        /// </summary>
        public override void Write(IChannelHandlerContext ctx, object msg, IPromise promise)
        {
            var httpMsg    = msg as IHttpMessage;
            var contentMsg = msg as IHttpContent;

            if (httpMsg is null && contentMsg is null)
            {
                _ = ctx.WriteAsync(msg, promise);
                return;
            }

            var release           = true;
            var promiseAggregator = new SimplePromiseAggregator(promise);

            try
            {
                var encoder   = Encoder;
                var endStream = false;
                if (httpMsg is object)
                {
                    // Provide the user the opportunity to specify the streamId
                    _currentStreamId = GetStreamId(httpMsg.Headers);

                    // Convert and write the headers.
                    var http2Headers = HttpConversionUtil.ToHttp2Headers(httpMsg, _validateHeaders);
                    endStream = msg is IFullHttpMessage fullHttpMsg && !fullHttpMsg.Content.IsReadable();
                    WriteHeaders(ctx, encoder, _currentStreamId, httpMsg.Headers, http2Headers, endStream, promiseAggregator);
                }

                if (!endStream && contentMsg is object)
                {
                    var           isLastContent = false;
                    HttpHeaders   trailers      = EmptyHttpHeaders.Default;
                    IHttp2Headers http2Trailers = EmptyHttp2Headers.Instance;
                    if (msg is ILastHttpContent lastContentMsg)
                    {
                        isLastContent = true;

                        // Convert any trailing headers.
                        trailers      = lastContentMsg.TrailingHeaders;
                        http2Trailers = HttpConversionUtil.ToHttp2Headers(trailers, _validateHeaders);
                    }

                    // Write the data
                    var content = contentMsg.Content;
                    endStream = isLastContent && trailers.IsEmpty;
                    _         = encoder.WriteDataAsync(ctx, _currentStreamId, content, 0, endStream, promiseAggregator.NewPromise());
                    release   = false;

                    if (!trailers.IsEmpty)
                    {
                        // Write trailing headers.
                        WriteHeaders(ctx, encoder, _currentStreamId, trailers, http2Trailers, true, promiseAggregator);
                    }
                }
            }
            catch (Exception t)
            {
                OnError(ctx, true, t);
                promiseAggregator.SetException(t);
            }
            finally
            {
                if (release)
                {
                    _ = ReferenceCountUtil.Release(msg);
                }
                _ = promiseAggregator.DoneAllocatingPromises();
            }
        }
Example #8
0
        public virtual Task WriteDataAsync(IChannelHandlerContext ctx, int streamId, IByteBuffer data, int padding, bool endOfStream, IPromise promise)
        {
            SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise);
            IByteBuffer             frameHeader       = null;

            try
            {
                if ((uint)(streamId - 1) > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.StreamID);
                }
                Http2CodecUtil.VerifyPadding(padding);

                int        remainingData = data.ReadableBytes;
                Http2Flags flags         = new Http2Flags();
                _ = flags.EndOfStream(false);
                _ = flags.PaddingPresent(false);
                // Fast path to write frames of payload size maxFrameSize first.
                if (remainingData > _maxFrameSize)
                {
                    frameHeader = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength);
                    Http2CodecUtil.WriteFrameHeaderInternal(frameHeader, _maxFrameSize, Http2FrameTypes.Data, flags, streamId);
                    do
                    {
                        // Write the header.
                        _ = ctx.WriteAsync(frameHeader.RetainedSlice(), promiseAggregator.NewPromise());

                        // Write the payload.
                        _ = ctx.WriteAsync(data.ReadRetainedSlice(_maxFrameSize), promiseAggregator.NewPromise());

                        remainingData -= _maxFrameSize;
                        // Stop iterating if remainingData ==  _maxFrameSize so we can take care of reference counts below.
                    }while (remainingData > _maxFrameSize);
                }

                if (0u >= (uint)padding)
                {
                    // Write the header.
                    if (frameHeader is object)
                    {
                        _           = frameHeader.Release();
                        frameHeader = null;
                    }

                    IByteBuffer frameHeader2 = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength);
                    _ = flags.EndOfStream(endOfStream);
                    Http2CodecUtil.WriteFrameHeaderInternal(frameHeader2, remainingData, Http2FrameTypes.Data, flags, streamId);
                    _ = ctx.WriteAsync(frameHeader2, promiseAggregator.NewPromise());

                    // Write the payload.
                    IByteBuffer lastFrame = data.ReadSlice(remainingData);
                    data = null;
                    _    = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise());
                }
                else
                {
                    if (remainingData != _maxFrameSize)
                    {
                        if (frameHeader is object)
                        {
                            _           = frameHeader.Release();
                            frameHeader = null;
                        }
                    }
                    else
                    {
                        remainingData -= _maxFrameSize;
                        // Write the header.
                        IByteBuffer lastFrame;
                        if (frameHeader is null)
                        {
                            lastFrame = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength);
                            Http2CodecUtil.WriteFrameHeaderInternal(lastFrame, _maxFrameSize, Http2FrameTypes.Data, flags, streamId);
                        }
                        else
                        {
                            lastFrame   = frameHeader.Slice();
                            frameHeader = null;
                        }

                        _ = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise());

                        // Write the payload.
                        lastFrame = data.ReadableBytes != _maxFrameSize?data.ReadSlice(_maxFrameSize) : data;

                        data = null;
                        _    = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise());
                    }

                    do
                    {
                        int frameDataBytes    = Math.Min(remainingData, _maxFrameSize);
                        int framePaddingBytes = Math.Min(padding, Math.Max(0, (_maxFrameSize - 1) - frameDataBytes));

                        // Decrement the remaining counters.
                        padding       -= framePaddingBytes;
                        remainingData -= frameDataBytes;

                        // Write the header.
                        IByteBuffer frameHeader2 = ctx.Allocator.Buffer(Http2CodecUtil.DataFrameHeaderLength);
                        _ = flags.EndOfStream(endOfStream && 0u >= (uint)remainingData && 0u >= (uint)padding);
                        _ = flags.PaddingPresent(framePaddingBytes > 0);
                        Http2CodecUtil.WriteFrameHeaderInternal(frameHeader2, framePaddingBytes + frameDataBytes, Http2FrameTypes.Data, flags, streamId);
                        WritePaddingLength(frameHeader2, framePaddingBytes);
                        _ = ctx.WriteAsync(frameHeader2, promiseAggregator.NewPromise());

                        // Write the payload.
                        if (frameDataBytes != 0)
                        {
                            if (0u >= (uint)remainingData)
                            {
                                IByteBuffer lastFrame = data.ReadSlice(frameDataBytes);
                                data = null;
                                _    = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise());
                            }
                            else
                            {
                                _ = ctx.WriteAsync(data.ReadRetainedSlice(frameDataBytes), promiseAggregator.NewPromise());
                            }
                        }

                        // Write the frame padding.
                        if (PaddingBytes(framePaddingBytes) > 0)
                        {
                            _ = ctx.WriteAsync(
                                ZeroBuffer.Slice(0, PaddingBytes(framePaddingBytes)),
                                promiseAggregator.NewPromise());
                        }
                    }while (remainingData != 0 || padding != 0);
                }
            }
            catch (Exception cause)
            {
                if (frameHeader is object)
                {
                    _ = frameHeader.Release();
                }

                // Use a try/finally here in case the data has been released before calling this method. This is not
                // necessary above because we internally allocate frameHeader.
                try
                {
                    if (data is object)
                    {
                        _ = data.Release();
                    }
                }
                finally
                {
                    promiseAggregator.SetException(cause);
                    _ = promiseAggregator.DoneAllocatingPromises();
                }

                return(promiseAggregator.Task);
            }

            _ = promiseAggregator.DoneAllocatingPromises();
            return(promiseAggregator.Task);
        }
Example #9
0
        Task WriteHeadersInternal(IChannelHandlerContext ctx, int streamId,
                                  IHttp2Headers headers, int padding, bool endOfStream, bool hasPriority,
                                  int streamDependency, short weight, bool exclusive, IPromise promise)
        {
            IByteBuffer             headerBlock       = null;
            SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise);

            try
            {
                if ((uint)(streamId - 1) > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.StreamID);
                }
                if (hasPriority)
                {
                    if (streamDependency < 0)
                    {
                        ThrowHelper.ThrowArgumentException_PositiveOrZero(ExceptionArgument.StreamDependency);
                    }
                    Http2CodecUtil.VerifyPadding(padding);
                    VerifyWeight(weight);
                }

                // Encode the entire header block.
                headerBlock = ctx.Allocator.Buffer();
                _headersEncoder.EncodeHeaders(streamId, headers, headerBlock);

                Http2Flags flags =
                    new Http2Flags().EndOfStream(endOfStream).PriorityPresent(hasPriority).PaddingPresent(padding > 0);

                // Read the first fragment (possibly everything).
                int         nonFragmentBytes  = padding + flags.GetNumPriorityBytes();
                int         maxFragmentLength = _maxFrameSize - nonFragmentBytes;
                IByteBuffer fragment          = headerBlock.ReadRetainedSlice(Math.Min(headerBlock.ReadableBytes, maxFragmentLength));

                // Set the end of headers flag for the first frame.
                _ = flags.EndOfHeaders(!headerBlock.IsReadable());

                int         payloadLength = fragment.ReadableBytes + nonFragmentBytes;
                IByteBuffer buf           = ctx.Allocator.Buffer(Http2CodecUtil.HeadersFrameHeaderLength);
                Http2CodecUtil.WriteFrameHeaderInternal(buf, payloadLength, Http2FrameTypes.Headers, flags, streamId);
                WritePaddingLength(buf, padding);

                if (hasPriority)
                {
                    _ = buf.WriteInt(exclusive ? (int)(uint)(0x80000000L | streamDependency) : streamDependency);

                    // Adjust the weight so that it fits into a single byte on the wire.
                    _ = buf.WriteByte(weight - 1);
                }

                _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise());

                // Write the first fragment.
                _ = ctx.WriteAsync(fragment, promiseAggregator.NewPromise());

                // Write out the padding, if any.
                if (PaddingBytes(padding) > 0)
                {
                    _ = ctx.WriteAsync(ZeroBuffer.Slice(0, PaddingBytes(padding)), promiseAggregator.NewPromise());
                }

                if (!flags.EndOfHeaders())
                {
                    _ = WriteContinuationFramesAsync(ctx, streamId, headerBlock, promiseAggregator);
                }
            }
            catch (Http2Exception e)
            {
                promiseAggregator.SetException(e);
            }
            catch (Exception t)
            {
                promiseAggregator.SetException(t);
                _ = promiseAggregator.DoneAllocatingPromises();
                throw;
            }
            finally
            {
                _ = (headerBlock?.Release());
            }

            _ = promiseAggregator.DoneAllocatingPromises();
            return(promiseAggregator.Task);
        }
Example #10
0
        public virtual Task WritePushPromiseAsync(IChannelHandlerContext ctx, int streamId,
                                                  int promisedStreamId, IHttp2Headers headers, int padding, IPromise promise)
        {
            IByteBuffer             headerBlock       = null;
            SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise);

            try
            {
                if ((uint)(streamId - 1) > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.StreamID);
                }
                if (promisedStreamId <= 0)
                {
                    ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.PromisedStreamId);
                }
                Http2CodecUtil.VerifyPadding(padding);

                // Encode the entire header block into an intermediate buffer.
                headerBlock = ctx.Allocator.Buffer();
                _headersEncoder.EncodeHeaders(streamId, headers, headerBlock);

                // Read the first fragment (possibly everything).
                Http2Flags flags = new Http2Flags().PaddingPresent(padding > 0);
                // IntFieldLength is for the length of the promisedStreamId
                int         nonFragmentLength = Http2CodecUtil.IntFieldLength + padding;
                int         maxFragmentLength = _maxFrameSize - nonFragmentLength;
                IByteBuffer fragment          = headerBlock.ReadRetainedSlice(Math.Min(headerBlock.ReadableBytes, maxFragmentLength));

                _ = flags.EndOfHeaders(!headerBlock.IsReadable());

                int         payloadLength = fragment.ReadableBytes + nonFragmentLength;
                IByteBuffer buf           = ctx.Allocator.Buffer(Http2CodecUtil.PushPromiseFrameHeaderLength);
                Http2CodecUtil.WriteFrameHeaderInternal(buf, payloadLength, Http2FrameTypes.PushPromise, flags, streamId);
                WritePaddingLength(buf, padding);

                // Write out the promised stream ID.
                _ = buf.WriteInt(promisedStreamId);
                _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise());

                // Write the first fragment.
                _ = ctx.WriteAsync(fragment, promiseAggregator.NewPromise());

                // Write out the padding, if any.
                if (PaddingBytes(padding) > 0)
                {
                    _ = ctx.WriteAsync(ZeroBuffer.Slice(0, PaddingBytes(padding)), promiseAggregator.NewPromise());
                }

                if (!flags.EndOfHeaders())
                {
                    _ = WriteContinuationFramesAsync(ctx, streamId, headerBlock, promiseAggregator);
                }
            }
            catch (Http2Exception e)
            {
                promiseAggregator.SetException(e);
            }
            catch (Exception t)
            {
                promiseAggregator.SetException(t);
                _ = promiseAggregator.DoneAllocatingPromises();
                throw;
            }
            finally
            {
                if (headerBlock is object)
                {
                    _ = headerBlock.Release();
                }
            }

            _ = promiseAggregator.DoneAllocatingPromises();
            return(promiseAggregator.Task);
        }