Example #1
0
 private void FreeReceiveQueue(ReceiveQueueItem item)
 {
     while (item != null && item.Buffer != null)
     {
         connection.config.BufferPool.Return(item.Buffer);
         item.Buffer = null;
         var current = item;
         item         = item.Next;
         current.Next = null;
     }
 }
Example #2
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)
                {
                    return(writeResetTask);
                }
                state = StreamState.Reset;
                var head = receiveQueueHead;
                receiveQueueHead = null;
                FreeReceiveQueue(head);
            }

            if (!fromRemote)
            {
                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
            {
                connection.writer.RemoveStream(this.Id);
            }

            if (!fromRemote)
            {
                this.connection.UnregisterStream(this);
            }

            readDataPossible.Set();
            readTrailersPossible.Set();
            readHeadersPossible.Set();

            return(writeResetTask);
        }
Example #3
0
 private void EnqueueReceiveQueueItem(ReceiveQueueItem newItem)
 {
     if (receiveQueueHead == null)
     {
         receiveQueueHead = newItem;
     }
     else
     {
         var current = receiveQueueHead;
         var next    = receiveQueueHead.Next;
         while (next != null)
         {
             current = next;
             next    = current.Next;
         }
         current.Next = newItem;
     }
 }
Example #4
0
        /// <summary>
        /// 处理数据帧的接收。
        /// </summary>
        /// <param name="buffer"></param>
        /// <param name="endOfStream"></param>
        /// <param name="tookBufferOwnership"></param>
        /// <returns></returns>
        public Http2Error?PushBuffer(
            ArraySegment <byte> buffer,
            bool endOfStream,
            out bool tookBufferOwnership)
        {
            tookBufferOwnership = false;
            var wakeupDataWaiter    = false;
            var wakeupTrailerWaiter = false;
            var removeStream        = false;

            lock (stateMutex)
            {
                switch (state)
                {
                case StreamState.ReservedLocal:
                case StreamState.ReservedRemote:
                    throw new NotImplementedException();

                case StreamState.Open:
                case StreamState.HalfClosedLocal:
                    if (headersReceived != HeaderReceptionState.ReceivedAllHeaders)
                    {
                        return(new Http2Error
                        {
                            StreamId = Id,
                            Code = ErrorCode.ProtocolError,
                            Message = "在所有头之前接收到数据",
                        });
                    }

                    if (buffer.Count > 0)
                    {
                        if (buffer.Count > receiveWindow)
                        {
                            return(new Http2Error
                            {
                                StreamId = Id,
                                Code = ErrorCode.FlowControlError,
                                Message = "超过接收窗口",
                            });
                        }
                        receiveWindow -= buffer.Count;
                        var newItem = new ReceiveQueueItem(buffer);
                        EnqueueReceiveQueueItem(newItem);
                        wakeupDataWaiter    = true;
                        tookBufferOwnership = true;
                    }

                    dataReceived = true;


                    totalInData += buffer.Count;
                    if (endOfStream &&
                        declaredInContentLength >= 0 &&
                        declaredInContentLength != totalInData)
                    {
                        return(new Http2Error
                        {
                            StreamId = Id,
                            Code = ErrorCode.ProtocolError,
                            Message =
                                "数据帧的长度与内容长度不匹配",
                        });
                    }

                    if (endOfStream)
                    {
                        if (state == StreamState.HalfClosedLocal)
                        {
                            state        = StreamState.Closed;
                            removeStream = true;
                        }
                        else
                        {
                            state = StreamState.HalfClosedRemote;
                        }
                        wakeupTrailerWaiter = true;
                        wakeupDataWaiter    = true;
                    }
                    break;

                case StreamState.Idle:
                case StreamState.HalfClosedRemote:
                case StreamState.Closed:

                    return(new Http2Error
                    {
                        StreamId = Id,
                        Code = ErrorCode.StreamClosed,
                        Message = "已接收封闭流的数据",
                    });

                case StreamState.Reset:

                    break;

                default:
                    throw new Exception("未处理的流状态");
                }
            }


            if (wakeupDataWaiter)
            {
                readDataPossible.Set();
            }
            if (wakeupTrailerWaiter)
            {
                readTrailersPossible.Set();
            }

            if (removeStream)
            {
                connection.UnregisterStream(this);
            }

            return(null);
        }
Example #5
0
        public async ValueTask <StreamReadResult> ReadAsync(ArraySegment <byte> buffer)
        {
            while (true)
            {
                await readDataPossible;

                int windowUpdateAmount     = 0;
                StreamReadResult result    = new StreamReadResult();
                bool             hasResult = false;

                lock (stateMutex)
                {
                    if (state == StreamState.Reset)
                    {
                        throw new StreamResetException();
                    }

                    var streamClosedFromRemote =
                        state == StreamState.Closed || state == StreamState.HalfClosedRemote;

                    if (receiveQueueHead != null)
                    {
                        var offset = buffer.Offset;
                        var count  = buffer.Count;
                        while (receiveQueueHead != null && count > 0)
                        {
                            var toCopy = Math.Min(receiveQueueHead.Count, count);
                            Array.Copy(
                                receiveQueueHead.Buffer, receiveQueueHead.Offset,
                                buffer.Array, offset,
                                toCopy);
                            offset += toCopy;
                            count  -= toCopy;

                            if (toCopy == receiveQueueHead.Count)
                            {
                                connection.config.BufferPool.Return(
                                    receiveQueueHead.Buffer);
                                receiveQueueHead = receiveQueueHead.Next;
                            }
                            else
                            {
                                receiveQueueHead.Offset += toCopy;
                                receiveQueueHead.Count  -= toCopy;
                                break;
                            }
                        }
                        if (!streamClosedFromRemote)
                        {
                            var isFree = totalReceiveWindow - ReceiveQueueLength;
                            var possibleWindowUpdate = isFree - receiveWindow;
                            if (possibleWindowUpdate >= (totalReceiveWindow / 2))
                            {
                                windowUpdateAmount = possibleWindowUpdate;
                                receiveWindow     += windowUpdateAmount;
                            }
                        }

                        result = new StreamReadResult
                        {
                            BytesRead   = offset - buffer.Offset,
                            EndOfStream = false,
                        };
                        hasResult = true;

                        if (receiveQueueHead == null && !streamClosedFromRemote)
                        {
                            readDataPossible.Reset();
                        }
                    }
                    else if (streamClosedFromRemote)
                    {
                        result = new StreamReadResult
                        {
                            BytesRead   = 0,
                            EndOfStream = true,
                        };
                        hasResult = true;
                    }
                }

                if (hasResult)
                {
                    if (windowUpdateAmount > 0)
                    {
                        await SendWindowUpdate(windowUpdateAmount);
                    }
                    return(result);
                }
            }
        }
Example #6
0
        /// <summary>
        /// Processes the reception of a DATA frame.
        /// The connection is responsible for checking the maximum frame length
        /// before calling this function.
        /// </summary>
        public Http2Error?PushBuffer(
            ArraySegment <byte> buffer,
            bool endOfStream,
            out bool tookBufferOwnership)
        {
            tookBufferOwnership = false;
            var wakeupDataWaiter    = false;
            var wakeupTrailerWaiter = false;
            var removeStream        = false;

            lock (stateMutex)
            {
                // Data frames are not valid in all states
                switch (state)
                {
                case StreamState.ReservedLocal:
                case StreamState.ReservedRemote:
                    // Push promises are currently not implemented
                    // At the moment these should already been
                    // rejected in the Connection.
                    // This needs to be reviewed later on
                    throw new NotImplementedException();

                case StreamState.Open:
                case StreamState.HalfClosedLocal:
                    if (headersReceived != HeaderReceptionState.ReceivedAllHeaders)
                    {
                        // Received DATA without HEADERS before.
                        // State Open can also mean we only have sent
                        // headers but not received them.
                        // Therefore checking the state alone isn't sufficient.
                        return(new Http2Error
                        {
                            StreamId = Id,
                            Code = ErrorCode.ProtocolError,
                            Message = "Received data before all headers",
                        });
                    }

                    // Only enqueue DATA frames with a real content length
                    // of at least 1 byte, otherwise the reader is needlessly
                    // woken up.
                    // Empty DATA frames are also not checked against the
                    // flow control window, which means they are valid even
                    // in case of a negative flow control window (which is
                    // not possible in the current state of the implementation).
                    // However we still treat empty DATA frames as a valid
                    // separator between HEADERS and trailing HEADERS.
                    if (buffer.Count > 0)
                    {
                        // Check if the flow control window is exceeded
                        if (buffer.Count > receiveWindow)
                        {
                            return(new Http2Error
                            {
                                StreamId = Id,
                                Code = ErrorCode.FlowControlError,
                                Message = "Received window exceeded",
                            });
                        }
                        if (connection.logger != null &&
                            connection.logger.IsEnabled(LogLevel.Trace))
                        {
                            connection.logger.LogTrace(
                                "Incoming flow control window update:\n" +
                                "  Stream {0} window: {1} -> {2}",
                                Id, receiveWindow, receiveWindow - buffer.Count);
                        }
                        receiveWindow -= buffer.Count;

                        // Enqueue the data at the end of the receive queue
                        // TODO: Instead of appending buffers with only small
                        // content on the end of each other we might to concat
                        // the buffers together, which avoids the worst-case
                        // scenario: The remote side sending us lots of 1byte
                        // DATA frames, where we need a queue item for each
                        // one.
                        var newItem = new ReceiveQueueItem(buffer);
                        EnqueueReceiveQueueItem(newItem);
                        wakeupDataWaiter    = true;
                        tookBufferOwnership = true;
                    }
                    // Allow trailing headers afterwards
                    dataReceived = true;

                    // Check if data matches declared content-length
                    // TODO: What should be done if the declared
                    // content-length was invalid (-2)?
                    totalInData += buffer.Count;
                    if (endOfStream &&
                        declaredInContentLength >= 0 &&
                        declaredInContentLength != totalInData)
                    {
                        return(new Http2Error
                        {
                            StreamId = Id,
                            Code = ErrorCode.ProtocolError,
                            Message =
                                "Length of DATA frames does not match content-length",
                        });
                    }

                    // All checks OK so far, wakeup waiter and handle state changes
                    // Handle state changes that are caused by DATA frames
                    if (endOfStream)
                    {
                        if (state == StreamState.HalfClosedLocal)
                        {
                            state        = StreamState.Closed;
                            removeStream = true;
                        }
                        else     // Open
                        {
                            state = StreamState.HalfClosedRemote;
                        }
                        wakeupTrailerWaiter = true;
                        wakeupDataWaiter    = true;
                    }
                    break;

                case StreamState.Idle:
                case StreamState.HalfClosedRemote:
                case StreamState.Closed:
                    // Received a DATA frame for a stream that was
                    // already closed or not properly opened from remote side.
                    // That's not valid
                    return(new Http2Error
                    {
                        StreamId = Id,
                        Code = ErrorCode.StreamClosed,
                        Message = "Received data for closed stream",
                    });

                case StreamState.Reset:
                    // The stream was already reset
                    // What we really should do here depends on the previous state,
                    // which is not stored for efficiency. If we reset the
                    // stream late headers are ok. If the remote resetted it
                    // this is a protocol error for the stream.
                    // As it does not really matter just ignore the frame.
                    break;

                default:
                    throw new Exception("Unhandled stream state");
                }
            }

            // Wakeup any blocked calls that are waiting on data or end of stream
            if (wakeupDataWaiter)
            {
                // Wakeup any blocked call that waits for headers to get available
                readDataPossible.Set();
            }
            if (wakeupTrailerWaiter)
            {
                readTrailersPossible.Set();
            }

            if (removeStream)
            {
                connection.UnregisterStream(this);
            }

            return(null);
        }
Example #7
0
        public async ValueTask <StreamReadResult> ReadAsync(ArraySegment <byte> buffer)
        {
            while (true)
            {
                await readDataPossible;

                int windowUpdateAmount     = 0;
                StreamReadResult result    = new StreamReadResult();
                bool             hasResult = false;

                lock (stateMutex)
                {
                    if (state == StreamState.Reset)
                    {
                        throw new StreamResetException();
                    }

                    var streamClosedFromRemote =
                        state == StreamState.Closed || state == StreamState.HalfClosedRemote;

                    if (receiveQueueHead != null)
                    {
                        // Copy as much data as possible from internal queue into
                        // user buffer
                        var offset = buffer.Offset;
                        var count  = buffer.Count;
                        while (receiveQueueHead != null && count > 0)
                        {
                            // Copy data from receive buffer to target
                            var toCopy = Math.Min(receiveQueueHead.Count, count);
                            Array.Copy(
                                receiveQueueHead.Buffer, receiveQueueHead.Offset,
                                buffer.Array, offset,
                                toCopy);
                            offset += toCopy;
                            count  -= toCopy;

                            if (toCopy == receiveQueueHead.Count)
                            {
                                connection.config.BufferPool.Return(
                                    receiveQueueHead.Buffer);
                                receiveQueueHead = receiveQueueHead.Next;
                            }
                            else
                            {
                                receiveQueueHead.Offset += toCopy;
                                receiveQueueHead.Count  -= toCopy;
                                break;
                            }
                        }

                        // Calculate whether we should send a window update frame
                        // after the read is complete.
                        // Only need to do this if the stream has not yet ended
                        if (!streamClosedFromRemote)
                        {
                            var isFree = totalReceiveWindow - ReceiveQueueLength;
                            var possibleWindowUpdate = isFree - receiveWindow;
                            if (possibleWindowUpdate >= (totalReceiveWindow / 2))
                            {
                                windowUpdateAmount = possibleWindowUpdate;
                                receiveWindow     += windowUpdateAmount;

                                if (connection.logger != null &&
                                    connection.logger.IsEnabled(LogLevel.Trace))
                                {
                                    connection.logger.LogTrace(
                                        "Incoming flow control window update:\n" +
                                        "  Stream {0} window: {1} -> {2}",
                                        Id, receiveWindow - windowUpdateAmount, receiveWindow);
                                }
                            }
                        }

                        result = new StreamReadResult {
                            BytesRead   = offset - buffer.Offset,
                            EndOfStream = false,
                        };
                        hasResult = true;

                        if (receiveQueueHead == null && !streamClosedFromRemote)
                        {
                            // If all data was consumed the next read must be blocked
                            // until more data comes in or the stream gets closed or reset
                            readDataPossible.Reset();
                        }
                    }
                    else if (streamClosedFromRemote)
                    {
                        // Deliver a notification that the stream was closed
                        result = new StreamReadResult {
                            BytesRead   = 0,
                            EndOfStream = true,
                        };
                        hasResult = true;
                    }
                }

                if (hasResult)
                {
                    if (windowUpdateAmount > 0)
                    {
                        // We need to send a window update frame before delivering
                        // the result
                        await SendWindowUpdate(windowUpdateAmount);
                    }
                    return(result);
                }
            }
        }
Example #8
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);
        }