public async Task WriteAsync(ArraySegment <byte> buffer, bool endOfStream = false) { var removeStream = false; await writeMutex.WaitAsync(); try { lock (stateMutex) { // Check the current stream state if (state == StreamState.Reset) { throw new StreamResetException(); } else if (state != StreamState.Open && state != StreamState.HalfClosedRemote) { throw new Exception("Attempt to write data in invalid stream state"); } else if (state == StreamState.Open && endOfStream) { state = StreamState.HalfClosedLocal; } else if (state == StreamState.HalfClosedRemote && endOfStream) { state = StreamState.Closed; removeStream = true; } // Besides state check also check if headers have already been sent // StreamState.Open could mean we only have received them if (!this.headersSent) { throw new Exception("Attempted to write data before headers"); } // Remark: There's no need to check whether trailers have already // been sent as writing trailers will (half)close the stream, // which is checked for dataSent = true; // Set a flag do disallow following headers } // Remark: // As we hold the writeMutex nobody can close the stream or send trailers // in between. // The only thing that may happen is that the stream get's reset in between, // which would be reported through the ConnectionWriter to us var fh = new FrameHeader { StreamId = this.Id, Type = FrameType.Data, Flags = endOfStream ? ((byte)DataFrameFlags.EndOfStream) : (byte)0, }; var res = await connection.writer.WriteData(fh, buffer); if (res == ConnectionWriter.WriteResult.StreamResetError) { throw new StreamResetException(); } else if (res != ConnectionWriter.WriteResult.Success) { throw new Exception("Can not write to stream"); // TODO: Improve me } } finally { writeMutex.Release(); if (removeStream) { connection.UnregisterStream(this); } } }
/// <summary> /// Reads and decodes a header block which consists of a single HEADER /// frame and 0 or more CONTINUATION frames. /// </summary> /// <param name="firstHeader"> /// The frame header of the HEADER frame which indicates that headers /// must be read. /// </param> public async ValueTask <Result> ReadHeaders( FrameHeader firstHeader, Func <int, byte[]> ensureBuffer) { // Check maximum frame size if (firstHeader.Length > maxFrameSize) { return(new Result { Error = new Http2Error { StreamId = 0, Code = ErrorCode.FrameSizeError, Message = "Maximum frame size exceeded", }, }); } PriorityData?prioData = null; var allowedHeadersSize = maxHeaderFieldsSize; var headers = new List <HeaderField>(); var initialFlags = firstHeader.Flags; var f = (HeadersFrameFlags)firstHeader.Flags; var isEndOfStream = f.HasFlag(HeadersFrameFlags.EndOfStream); var isEndOfHeaders = f.HasFlag(HeadersFrameFlags.EndOfHeaders); var isPadded = f.HasFlag(HeadersFrameFlags.Padded); var hasPriority = f.HasFlag(HeadersFrameFlags.Priority); // Do a first check whether frame is big enough for the given flags var minLength = 0; if (isPadded) { minLength += 1; } if (hasPriority) { minLength += 5; } if (firstHeader.Length < minLength) { return(new Result { Error = new Http2Error { StreamId = 0, Code = ErrorCode.ProtocolError, Message = "Invalid frame content size", }, }); } // Get a buffer for the initial frame // TODO: We now always read the initial frame at once, but we could // split it up into multiple smaller reads if the receive buffer is // smaller. The code needs to be slightly adapted for this. byte[] buffer = ensureBuffer(firstHeader.Length); // Read the content of the initial frame await reader.ReadAll(new ArraySegment <byte>(buffer, 0, firstHeader.Length)); var offset = 0; var padLen = 0; if (isPadded) { // Extract padding Length padLen = buffer[0]; offset++; } if (hasPriority) { // Extract priority prioData = PriorityData.DecodeFrom( new ArraySegment <byte>(buffer, offset, 5)); offset += 5; } var contentLen = firstHeader.Length - offset - padLen; if (contentLen < 0) { return(new Result { Error = new Http2Error { StreamId = 0, Code = ErrorCode.ProtocolError, Message = "Invalid frame content size", }, }); } // Allow table updates at the start of header header block // This will be reset once the first header was decoded and will // persist also during the continuation frame hpackDecoder.AllowTableSizeUpdates = true; // Decode headers from the first header block var decodeResult = hpackDecoder.DecodeHeaderBlockFragment( new ArraySegment <byte>(buffer, offset, contentLen), allowedHeadersSize, headers); var err = DecodeResultToError(decodeResult); if (err != null) { return(new Result { Error = err }); } allowedHeadersSize -= decodeResult.HeaderFieldsSize; while (!isEndOfHeaders) { // Read the next frame header // This must be a continuation frame // Remark: No need for ensureBuffer, since the buffer is // guaranteed to be larger than the first frameheader buffer var contHeader = await FrameHeader.ReceiveAsync(reader, buffer); if (logger != null && logger.IsEnabled(LogLevel.Trace)) { logger.LogTrace("recv " + FramePrinter.PrintFrameHeader(contHeader)); } if (contHeader.Type != FrameType.Continuation || contHeader.StreamId != firstHeader.StreamId || contHeader.Length > maxFrameSize || contHeader.Length == 0) { return(new Result { Error = new Http2Error { StreamId = 0, Code = ErrorCode.ProtocolError, Message = "Invalid continuation frame", }, }); } var contFlags = ((ContinuationFrameFlags)contHeader.Flags); isEndOfHeaders = contFlags.HasFlag(ContinuationFrameFlags.EndOfHeaders); // Read the HeaderBlockFragment of the continuation frame // TODO: We now always read the frame at once, but we could // split it up into multiple smaller reads if the receive buffer // is smaller. The code needs to be slightly adapted for this. buffer = ensureBuffer(contHeader.Length); await reader.ReadAll(new ArraySegment <byte>(buffer, 0, contHeader.Length)); offset = 0; contentLen = contHeader.Length; // Decode headers from continuation fragment decodeResult = hpackDecoder.DecodeHeaderBlockFragment( new ArraySegment <byte>(buffer, offset, contentLen), allowedHeadersSize, headers); var err2 = DecodeResultToError(decodeResult); if (err2 != null) { return(new Result { Error = err2 }); } allowedHeadersSize -= decodeResult.HeaderFieldsSize; } // Check if decoder is initial state, which means a complete header // block was received if (!hpackDecoder.HasInitialState) { return(new Result { Error = new Http2Error { Code = ErrorCode.CompressionError, StreamId = 0u, Message = "Received incomplete header block", }, }); } return(new Result { Error = null, HeaderData = new CompleteHeadersFrameData { StreamId = firstHeader.StreamId, Headers = headers, Priority = prioData, EndOfStream = isEndOfStream, }, }); }
internal ValueTask <ConnectionWriter.WriteResult> Reset( ErrorCode errorCode, bool fromRemote) { ValueTask <ConnectionWriter.WriteResult> writeResetTask = new ValueTask <ConnectionWriter.WriteResult>( ConnectionWriter.WriteResult.Success); lock (stateMutex) { if (state == StreamState.Reset || state == StreamState.Closed) { // Already reset or fully closed return(writeResetTask); } state = StreamState.Reset; // Free the receive queue var head = receiveQueueHead; receiveQueueHead = null; FreeReceiveQueue(head); } if (connection.logger != null) { connection.logger.LogTrace( "Resetted stream {0} with error code {1}", Id, errorCode); } // Remark: Even if we are here in IDLE state we need to send the // RESET frame. The reason for this is that if we receive a header // for a new stream which is invalid a StreamImpl instance will be // created and put into IDLE state. The header processing will fail // and Reset will get called. As the remote thinks we are in Open // state we must send a RST_STREAM. if (!fromRemote) { // Send a reset frame with the given error code var fh = new FrameHeader { StreamId = this.Id, Type = FrameType.ResetStream, Flags = 0, }; var resetData = new ResetFrameData { ErrorCode = errorCode }; writeResetTask = connection.writer.WriteResetStream(fh, resetData); } else { // If we don't send a notification we still have to unregister // from the writer in order to cancel pending writes connection.writer.RemoveStream(this.Id); } // Unregister from the connection // If this has happened from the remote side the connection will // already have performed this if (!fromRemote) { this.connection.UnregisterStream(this); } // Unblock all waiters readDataPossible.Set(); readTrailersPossible.Set(); readHeadersPossible.Set(); return(writeResetTask); }