Example #1
0
        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);
                }
            }
        }
Example #2
0
        /// <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,
                },
            });
        }
Example #3
0
        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);
        }