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); } }
/// <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); }
/// <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(); } }
// 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); } } }
/// <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); }
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); } }
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); } }
/// <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); }
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); } }
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); } }
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); }
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); } }
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); }
/// <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); }
/// <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); }
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)); } }
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); }
/// <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; } }
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)); } }
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); }
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); }
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; } }