Пример #1
0
        public virtual Task WriteRstStreamAsync(IChannelHandlerContext ctx, int streamId, Http2Error errorCode, IPromise promise)
        {
            try
            {
                if ((uint)(streamId - 1) > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.StreamID);
                }
                VerifyErrorCode((long)errorCode);

                IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.RstStreamFrameLength);
                Http2CodecUtil.WriteFrameHeaderInternal(buf, Http2CodecUtil.IntFieldLength, Http2FrameTypes.RstStream, new Http2Flags(), streamId);
                _ = buf.WriteInt((int)errorCode);
                return(ctx.WriteAsync(buf, promise));
            }
            catch (Exception t)
            {
                promise.SetException(t);
                return(promise.Task);
            }
        }
Пример #2
0
        /// <summary>
        /// Constructs a controller with the given settings.
        /// </summary>
        /// <param name="connection">the connection state.</param>
        /// <param name="windowUpdateRatio">the window percentage below which to send a <c>WINDOW_UPDATE</c>.</param>
        /// <param name="autoRefillConnectionWindow">if <c>true</c>, effectively disables the connection window
        /// in the flow control algorithm as they will always refill automatically without requiring the
        /// application to consume the bytes. When enabled, the maximum bytes you must be prepared to
        /// queue is proportional to <c>maximum number of concurrent streams * the initial window
        /// size per stream</c>
        /// (<a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_CONCURRENT_STREAMS</a>
        /// <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_INITIAL_WINDOW_SIZE</a>).
        /// </param>
        public DefaultHttp2LocalFlowController(IHttp2Connection connection, float windowUpdateRatio, bool autoRefillConnectionWindow)
        {
            if (connection is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.connection);
            }

            _connection = connection;
            WindowUpdateRatio(windowUpdateRatio);

            // Add a flow state for the connection.
            _stateKey = connection.NewKey();
            IFlowState connectionState = autoRefillConnectionWindow
                ? new AutoRefillState(this, connection.ConnectionStream, _initialWindowSize)
                : new DefaultState(this, connection.ConnectionStream, _initialWindowSize);

            _ = connection.ConnectionStream.SetProperty(_stateKey, connectionState);

            // Register for notification of new streams.
            connection.AddListener(this);
        }
Пример #3
0
        /// <inheritdoc />
        /// <remarks>Any queued <see cref="IHttp2RemoteFlowControlled"/> objects will be sent.</remarks>
        public void SetChannelHandlerContext(IChannelHandlerContext ctx)
        {
            if (ctx is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.ctx);
            }
            _ctx = ctx;

            // Writing the pending bytes will not check writability change and instead a writability change notification
            // to be provided by an explicit call.
            ChannelWritabilityChanged();

            // Don't worry about cleaning up queued frames here if ctx is null. It is expected that all streams will be
            // closed and the queue cleanup will occur when the stream state transitions occur.

            // If any frames have been queued up, we should send them now that we have a channel context.
            if (IsChannelWritable())
            {
                WritePendingBytes();
            }
        }
Пример #4
0
 // package-private for testing only
 internal static void SetHttp2Authority(string authority, IHttp2Headers output)
 {
     // The authority MUST NOT include the deprecated "userinfo" subcomponent
     if (authority is object)
     {
         if (StringUtil.IsEmpty(authority))
         {
             output.Authority = AsciiString.Empty;
         }
         else
         {
             int start  = authority.IndexOf('@') + 1;
             int length = authority.Length - start;
             if (0u >= (uint)length)
             {
                 ThrowHelper.ThrowArgumentException_Http2AuthorityIsEmpty(authority);
             }
             output.Authority = new AsciiString(authority, start, length);
         }
     }
 }
Пример #5
0
        /// <summary>
        /// Create a new object to contain the response data.
        /// </summary>
        /// <param name="streamId">The stream associated with the response</param>
        /// <param name="http2Headers">The initial set of HTTP/2 headers to create the response with</param>
        /// <param name="validateHttpHeaders"><c>true</c> to validate HTTP headers in the http-codec
        /// <para><c>false</c> not to validate HTTP headers in the http-codec</para></param>
        /// <returns>A new response object which represents headers for a chunked response</returns>
        /// <exception cref="Http2Exception">See <see cref="AddHttp2ToHttpHeaders(int, IHttp2Headers, HttpHeaders, DotNettyHttpVersion, bool, bool)"/></exception>
        public static IHttpResponse ToHttpResponse(int streamId, IHttp2Headers http2Headers, bool validateHttpHeaders)
        {
            var status = ParseStatus(http2Headers.Status);
            // HTTP/2 does not define a way to carry the version or reason phrase that is included in an
            // HTTP/1.1 status line.
            var msg = new DefaultHttpResponse(DotNettyHttpVersion.Http11, status, validateHttpHeaders);

            try
            {
                AddHttp2ToHttpHeaders(streamId, http2Headers, msg.Headers, msg.ProtocolVersion, false, true);
            }
            catch (Http2Exception)
            {
                throw;
            }
            catch (Exception t)
            {
                ThrowHelper.ThrowStreamError_Http2ToHttp1HeadersConversionError(streamId, t);
            }
            return(msg);
        }
Пример #6
0
        public virtual Task WriteWindowUpdateAsync(IChannelHandlerContext ctx, int streamId, int windowSizeIncrement, IPromise promise)
        {
            try
            {
                if (streamId < 0)
                {
                    ThrowHelper.ThrowArgumentException_PositiveOrZero(ExceptionArgument.StreamID);
                }
                VerifyWindowSizeIncrement(windowSizeIncrement);

                IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.WindowUpdateFrameLength);
                Http2CodecUtil.WriteFrameHeaderInternal(buf, Http2CodecUtil.IntFieldLength, Http2FrameTypes.WindowUpdate, new Http2Flags(), streamId);
                _ = buf.WriteInt(windowSizeIncrement);
                return(ctx.WriteAsync(buf, promise));
            }
            catch (Exception t)
            {
                promise.SetException(t);
                return(promise.Task);
            }
        }
Пример #7
0
        void ReadSettingsFrame(IChannelHandlerContext ctx, IByteBuffer payload, IHttp2FrameListener listener)
        {
            if (_flags.Ack())
            {
                listener.OnSettingsAckRead(ctx);
            }
            else
            {
                int           numSettings = _payloadLength / Http2CodecUtil.SettingEntryLength;
                Http2Settings settings    = new Http2Settings();
                for (int index = 0; index < numSettings; ++index)
                {
                    char id    = (char)payload.ReadUnsignedShort();
                    long value = payload.ReadUnsignedInt();
                    try
                    {
                        _ = settings.Put(id, value);
                    }
                    catch (ArgumentException e)
                    {
                        switch (id)
                        {
                        case Http2CodecUtil.SettingsMaxFrameSize:
                            ThrowHelper.ThrowConnectionError(Http2Error.ProtocolError, e);
                            break;

                        case Http2CodecUtil.SettingsInitialWindowSize:
                            ThrowHelper.ThrowConnectionError(Http2Error.FlowControlError, e);
                            break;

                        default:
                            ThrowHelper.ThrowConnectionError(Http2Error.ProtocolError, e);
                            break;
                        }
                    }
                }

                listener.OnSettingsRead(ctx, settings);
            }
        }
Пример #8
0
        /// <summary>
        /// Apply HTTP/2 rules while translating status code to <see cref="HttpResponseStatus"/>
        /// </summary>
        /// <param name="status">The status from an HTTP/2 frame</param>
        /// <returns>The HTTP/1.x status</returns>
        /// <exception cref="Http2Exception">If there is a problem translating from HTTP/2 to HTTP/1.x</exception>
        public static HttpResponseStatus ParseStatus(ICharSequence status)
        {
            HttpResponseStatus result = null;

            try
            {
                result = HttpResponseStatus.ParseLine(status);
                if (result == HttpResponseStatus.SwitchingProtocols)
                {
                    ThrowHelper.ThrowConnectionError_InvalidHttp2StatusCode(result.Code);
                }
            }
            catch (Http2Exception)
            {
                throw;
            }
            catch (Exception t)
            {
                ThrowHelper.ThrowConnectionError_UnrecognizedHttpStatusCode(t, status);
            }
            return(result);
        }
Пример #9
0
        void VerifyContinuationFrame()
        {
            VerifyAssociatedWithAStream();
            VerifyPayloadLength(_payloadLength);

            if (_headersContinuation is null)
            {
                ThrowHelper.ThrowConnectionError_ReceivedFrameButNotCurrentlyProcessingHeaders(_frameType);
            }

            var expectedStreamId = _headersContinuation.GetStreamId();

            if (_streamId != expectedStreamId)
            {
                ThrowHelper.ThrowConnectionError_ContinuationStreamIDDoesNotMatchPendingHeaders(expectedStreamId, _streamId);
            }

            if (_payloadLength < _flags.GetPaddingPresenceFieldLength())
            {
                ThrowHelper.ThrowStreamError_FrameLengthTooSmallForPadding(_streamId, _payloadLength);
            }
        }
Пример #10
0
        public void EncodeHeaders(int streamId, IHttp2Headers headers, IByteBuffer buffer)
        {
            try
            {
                // If there was a change in the table size, serialize the output from the hpackEncoder
                // resulting from that change.
                if (_tableSizeChangeOutput.IsReadable())
                {
                    _ = buffer.WriteBytes(_tableSizeChangeOutput);
                    _ = _tableSizeChangeOutput.Clear();
                }

                _hpackEncoder.EncodeHeaders(streamId, buffer, headers, _sensitivityDetector);
            }
            catch (Http2Exception)
            {
                throw;
            }
            catch (Exception t)
            {
                ThrowHelper.ThrowConnectionError_FailedEncodingHeadersBlock(t);
            }
        }
Пример #11
0
        private static CharSequenceMap <AsciiString> ToLowercaseMap(IEnumerable <ICharSequence> values, int arraySizeHint)
        {
            var valueConverter = UnsupportedValueConverter <AsciiString> .Instance;
            var result         = new CharSequenceMap <AsciiString>(true, valueConverter, arraySizeHint);

            foreach (var item in values)
            {
                AsciiString lowerCased = AsciiString.Of(item).ToLowerCase();
                try
                {
                    int index = lowerCased.ForEachByte(ByteProcessor.FindComma);
                    if (index != -1)
                    {
                        int start = 0;
                        do
                        {
                            _     = result.Add(lowerCased.SubSequence(start, index, false).Trim(), AsciiString.Empty);
                            start = index + 1;
                        } while (start < lowerCased.Count &&
                                 (index = lowerCased.ForEachByte(start, lowerCased.Count - start, ByteProcessor.FindComma)) != -1);
                        _ = result.Add(lowerCased.SubSequence(start, lowerCased.Count, false).Trim(), AsciiString.Empty);
                    }
                    else
                    {
                        _ = result.Add(lowerCased.Trim(), AsciiString.Empty);
                    }
                }
                catch (Exception)
                {
                    // This is not expect to happen because FIND_COMMA never throws but must be caught
                    // because of the ByteProcessor interface.
                    ThrowHelper.ThrowInvalidOperationException();
                }
            }

            return(result);
        }
Пример #12
0
            protected internal virtual void InitialWindowSize(int newWindowSize)
            {
                if ((uint)newWindowSize > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowArgumentException_PositiveOrZero(newWindowSize, ExceptionArgument.newWindowSize);
                }

                int delta = newWindowSize - _controller._initialWindowSize;

                _controller._initialWindowSize = newWindowSize;
                _ = _controller._connection.ForEachActiveStream(Visit);

                if (delta > 0 && _controller.IsChannelWritable())
                {
                    // The window size increased, send any pending frames for all streams.
                    WritePendingBytes();
                }

                bool Visit(IHttp2Stream stream)
                {
                    _ = _controller.GetState(stream).IncrementStreamWindow(delta);
                    return(true);
                }
            }
Пример #13
0
 public override Task WriteRstStreamAsync(IChannelHandlerContext ctx, int streamId, Http2Error errorCode, IPromise promise)
 {
     if (IsExistingStream(streamId))
     {
         return(base.WriteRstStreamAsync(ctx, streamId, errorCode, promise));
     }
     // Since the delegate doesn't know about any buffered streams we have to handle cancellation
     // of the promises and releasing of the ByteBufs here.
     if (_pendingStreams.TryGetValue(streamId, out var stream))
     {
         _ = _pendingStreams.Remove(streamId);
         // Sending a RST_STREAM to a buffered stream will succeed the promise of all frames
         // associated with the stream, as sending a RST_STREAM means that someone "doesn't care"
         // about the stream anymore and thus there is not point in failing the promises and invoking
         // error handling routines.
         stream.Close(null);
         promise.Complete();
     }
     else
     {
         promise.SetException(ThrowHelper.GetConnectionError_StreamDoesNotExist(streamId));
     }
     return(promise.Task);
 }
Пример #14
0
            /// <summary>
            /// Clears the pending queue and writes errors for each remaining frame.
            /// </summary>
            /// <param name="error">the <see cref="Http2Error"/> to use.</param>
            /// <param name="cause">the <see cref="Exception"/> that caused this method to be invoked.</param>
            internal void Cancel(Http2Error error, Exception cause = null)
            {
                _cancelled = true;
                // Ensure that the queue can't be modified while we are writing.
                if (_writing)
                {
                    return;
                }

                if (_pendingWriteQueue.TryRemoveFromFront(out IHttp2RemoteFlowControlled frame))
                {
                    // Only create exception once and reuse to reduce overhead of filling in the stacktrace.
                    Http2Exception exception = ThrowHelper.GetStreamError_StreamClosedBeforeWriteCouldTakePlace(
                        _stream.Id, error, cause);
                    do
                    {
                        WriteError(frame, exception);
                    } while (_pendingWriteQueue.TryRemoveFromFront(out frame));
                }

                _controller._streamByteDistributor.UpdateStreamableBytes(this);

                _controller._monitor.StateCancelled(this);
            }
Пример #15
0
            /// <summary>
            /// Determines the ratio between {@code numBytes} and <see cref="Http2Decompressor._decompressed"/>.
            /// This ratio is used to decrement <see cref="Http2Decompressor._decompressed"/> and
            /// <see cref="Http2Decompressor._compressed"/>.
            /// </summary>
            /// <param name="streamId">the stream ID</param>
            /// <param name="decompressedBytes">The number of post-decompressed bytes to return to flow control</param>
            /// <returns>The number of pre-decompressed bytes that have been consumed.</returns>
            public int ConsumeBytes(int streamId, int decompressedBytes)
            {
                if ((uint)decompressedBytes > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowArgumentException_PositiveOrZero(decompressedBytes, ExceptionArgument.decompressedBytes);
                }
                if ((uint)(_decompressed - decompressedBytes) > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowStreamError_AttemptingToReturnTooManyBytesForStream(
                        streamId, _decompressed, decompressedBytes);
                }
                double consumedRatio      = decompressedBytes / (double)_decompressed;
                int    consumedCompressed = Math.Min(_compressed, (int)Math.Ceiling(_compressed * consumedRatio));

                if ((uint)(_compressed - consumedCompressed) > SharedConstants.TooBigOrNegative)
                {
                    ThrowHelper.ThrowStreamError_OverflowWhenConvertingDecompressedBytesToCompressedBytesForStream(
                        streamId, decompressedBytes, _decompressed, _compressed, consumedCompressed);
                }
                _decompressed -= decompressedBytes;
                _compressed   -= consumedCompressed;

                return(consumedCompressed);
            }
Пример #16
0
            public bool ConsumeBytes(IHttp2Stream stream, int numBytes)
            {
                Http2Decompressor decompressor = _frameListener.Decompressor(stream);

                if (decompressor is object)
                {
                    // Convert the decompressed bytes to compressed (on the wire) bytes.
                    numBytes = decompressor.ConsumeBytes(stream.Id, numBytes);
                }
                try
                {
                    return(_flowController.ConsumeBytes(stream, numBytes));
                }
                catch (Http2Exception)
                {
                    throw;
                }
                catch (Exception t)
                {
                    // The stream should be closed at this point. We have already changed our state tracking the compressed
                    // bytes, and there is no guarantee we can recover if the underlying flow controller throws.
                    return(ThrowHelper.ThrowStreamError_ErrorWhileReturningBytesToFlowControlWindow(stream.Id, t));
                }
            }
Пример #17
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);
        }
Пример #18
0
        /// <summary>
        /// Processes all <see cref="IHttp2Frame"/>s. <see cref="IHttp2StreamFrame"/>s may only originate in child streams.
        /// </summary>
        /// <param name="ctx"></param>
        /// <param name="msg"></param>
        /// <param name="promise"></param>
        public override void Write(IChannelHandlerContext ctx, object msg, IPromise promise)
        {
            switch (msg)
            {
            case IHttp2DataFrame dataFrame:
                _ = Encoder.WriteDataAsync(ctx, dataFrame.Stream.Id, dataFrame.Content,
                                           dataFrame.Padding, dataFrame.IsEndStream, promise);
                break;

            case IHttp2HeadersFrame headersFrame:
                WriteHeadersFrame(ctx, headersFrame, promise);
                break;

            case IHttp2WindowUpdateFrame windowUpdateFrame:
                var frameStream = windowUpdateFrame.Stream;
                // It is legit to send a WINDOW_UPDATE frame for the connection stream. The parent channel doesn't attempt
                // to set the Http2FrameStream so we assume if it is null the WINDOW_UPDATE is for the connection stream.
                try
                {
                    if (frameStream is null)
                    {
                        IncreaseInitialConnectionWindow(windowUpdateFrame.WindowSizeIncrement);
                    }
                    else
                    {
                        _ = ConsumeBytes(frameStream.Id, windowUpdateFrame.WindowSizeIncrement);
                    }
                    promise.Complete();
                }
                catch (Exception t)
                {
                    promise.SetException(t);
                }
                break;

            case IHttp2ResetFrame rstFrame:
                int id = rstFrame.Stream.Id;
                // Only ever send a reset frame if stream may have existed before as otherwise we may send a RST on a
                // stream in an invalid state and cause a connection error.
                if (Connection.StreamMayHaveExisted(id))
                {
                    _ = Encoder.WriteRstStreamAsync(ctx, id, rstFrame.ErrorCode, promise);
                }
                else
                {
                    _ = ReferenceCountUtil.Release(rstFrame);
                    promise.SetException(GetStreamNeverExistedException(id));
                }
                break;

            case IHttp2PingFrame pingFrame:
                _ = Encoder.WritePingAsync(ctx, pingFrame.Ack, pingFrame.Content, promise);
                break;

            case IHttp2SettingsAckFrame _:
                // In the event of manual SETTINGS ACK is is assumed the encoder will apply the earliest received but not
                // yet ACKed settings.
                _ = Encoder.WriteSettingsAckAsync(ctx, promise);
                break;

            case IHttp2SettingsFrame settingsFrame:
                _ = Encoder.WriteSettingsAsync(ctx, settingsFrame.Settings, promise);
                break;

            case IHttp2GoAwayFrame goAwayFrame:
                WriteGoAwayFrame(ctx, goAwayFrame, promise);
                break;

            case IHttp2UnknownFrame unknownFrame:
                _ = Encoder.WriteFrameAsync(ctx, unknownFrame.FrameType, unknownFrame.Stream.Id,
                                            unknownFrame.Flags, unknownFrame.Content, promise);
                break;

            default:
                if (msg is IHttp2Frame)
                {
                    _ = ReferenceCountUtil.Release(msg);
                    ThrowHelper.ThrowUnsupportedMessageTypeException();
                }
                _ = ctx.WriteAsync(msg, promise);
                break;
            }
        }
Пример #19
0
        public override int OnDataRead(IChannelHandlerContext ctx, int streamId, IByteBuffer data, int padding, bool endOfStream)
        {
            IHttp2Stream      stream       = _connection.Stream(streamId);
            Http2Decompressor decompressor = Decompressor(stream);

            if (decompressor is null)
            {
                // The decompressor may be null if no compatible encoding type was found in this stream's headers
                return(_listener.OnDataRead(ctx, streamId, data, padding, endOfStream));
            }

            EmbeddedChannel channel         = decompressor.Decompressor;
            int             compressedBytes = data.ReadableBytes + padding;

            decompressor.IncrementCompressedBytes(compressedBytes);
            try
            {
                // call retain here as it will call release after its written to the channel
                _ = channel.WriteInbound(data.Retain());
                var buf = NextReadableBuf(channel);
                if (buf is null && endOfStream && channel.Finish())
                {
                    buf = NextReadableBuf(channel);
                }
                if (buf is null)
                {
                    if (endOfStream)
                    {
                        _ = _listener.OnDataRead(ctx, streamId, Unpooled.Empty, padding, true);
                    }
                    // No new decompressed data was extracted from the compressed data. This means the application could
                    // not be provided with data and thus could not return how many bytes were processed. We will assume
                    // there is more data coming which will complete the decompression block. To allow for more data we
                    // return all bytes to the flow control window (so the peer can send more data).
                    decompressor.IncrementDecompressedBytes(compressedBytes);
                    return(compressedBytes);
                }
                try
                {
                    IHttp2LocalFlowController flowController = _connection.Local.FlowController;
                    decompressor.IncrementDecompressedBytes(padding);
                    while (true)
                    {
                        var nextBuf = NextReadableBuf(channel);
                        var decompressedEndOfStream = nextBuf is null && endOfStream;
                        if (decompressedEndOfStream && channel.Finish())
                        {
                            nextBuf = NextReadableBuf(channel);
                            decompressedEndOfStream = nextBuf is null;
                        }

                        decompressor.IncrementDecompressedBytes(buf.ReadableBytes);
                        // Immediately return the bytes back to the flow controller. ConsumedBytesConverter will convert
                        // from the decompressed amount which the user knows about to the compressed amount which flow
                        // control knows about.
                        _ = flowController.ConsumeBytes(stream,
                                                        _listener.OnDataRead(ctx, streamId, buf, padding, decompressedEndOfStream));
                        if (nextBuf is null)
                        {
                            break;
                        }

                        padding = 0; // Padding is only communicated once on the first iteration.
                        _       = buf.Release();
                        buf     = nextBuf;
                    }
                    // We consume bytes each time we call the listener to ensure if multiple frames are decompressed
                    // that the bytes are accounted for immediately. Otherwise the user may see an inconsistent state of
                    // flow control.
                    return(0);
                }
                finally
                {
                    _ = buf.Release();
                }
            }
            catch (Http2Exception)
            {
                throw;
            }
            catch (Exception t)
            {
                return(ThrowHelper.ThrowStreamError_DecompressorErrorDetectedWhileDelegatingDataReadOnStream(stream.Id, t));
            }
        }
Пример #20
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);
        }
Пример #21
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);
        }
Пример #22
0
        void ProcessHeaderState(IByteBuffer input)
        {
            if (input.ReadableBytes < Http2CodecUtil.FrameHeaderLength)
            {
                // Wait until the entire frame header has been read.
                return;
            }

            // Read the header and prepare the unmarshaller to read the frame.
            _payloadLength = input.ReadUnsignedMedium();
            if (_payloadLength > _maxFrameSize)
            {
                ThrowHelper.ThrowConnectionError_FrameLengthExceedsMaximum(_payloadLength, _maxFrameSize);
            }

            _frameType = (Http2FrameTypes)input.ReadByte();
            _flags     = new Http2Flags(input.ReadByte());
            _streamId  = Http2CodecUtil.ReadUnsignedInt(input);

            // We have consumed the data, next time we read we will be expecting to read the frame payload.
            _readingHeaders = false;

            switch (_frameType)
            {
            case Http2FrameTypes.Data:
                VerifyDataFrame();
                break;

            case Http2FrameTypes.Headers:
                VerifyHeadersFrame();
                break;

            case Http2FrameTypes.Priority:
                VerifyPriorityFrame();
                break;

            case Http2FrameTypes.RstStream:
                VerifyRstStreamFrame();
                break;

            case Http2FrameTypes.Settings:
                VerifySettingsFrame();
                break;

            case Http2FrameTypes.PushPromise:
                VerifyPushPromiseFrame();
                break;

            case Http2FrameTypes.Ping:
                VerifyPingFrame();
                break;

            case Http2FrameTypes.GoAway:
                VerifyGoAwayFrame();
                break;

            case Http2FrameTypes.WindowUpdate:
                VerifyWindowUpdateFrame();
                break;

            case Http2FrameTypes.Continuation:
                VerifyContinuationFrame();
                break;

            default:
                // Unknown frame type, could be an extension.
                VerifyUnknownFrame();
                break;
            }
        }