예제 #1
0
        public void WriteHeadersFrame(HeadersList headers, bool isEndStream, bool isEndHeaders)
        {
            if (headers == null)
            {
                throw new ArgumentNullException("headers is null");
            }

            if (Closed)
            {
                return;
            }

            var frame = new HeadersFrame(_id, true)
            {
                IsEndHeaders = isEndHeaders,
                IsEndStream  = isEndStream,
                Headers      = headers,
            };

            _writeQueue.WriteFrame(frame);

            if (frame.IsEndStream)
            {
                HalfClosedLocal = true;
            }
            else if (ReservedLocal)
            {
                HalfClosedRemote = true;
            }

            if (OnFrameSent != null)
            {
                OnFrameSent(this, new FrameSentEventArgs(frame));
            }
        }
예제 #2
0
        //Outgoing
        internal Http2Stream(int id, WriteQueue writeQueue, FlowControlManager flowCrtlManager, int priority = Constants.DefaultStreamPriority)
        {
            if (id <= 0)
            {
                throw new ArgumentOutOfRangeException("invalid id for stream");
            }

            if (priority < 0 || priority > Constants.MaxPriority)
            {
                throw  new ArgumentOutOfRangeException("priority out of range");
            }

            _id              = id;
            Priority         = priority;
            _writeQueue      = writeQueue;
            _flowCrtlManager = flowCrtlManager;

            _unshippedFrames = new Queue <DataFrame>(16);
            Headers          = new HeadersList();

            SentDataAmount       = 0;
            ReceivedDataAmount   = 0;
            IsFlowControlBlocked = false;
            IsFlowControlEnabled = _flowCrtlManager.IsFlowControlEnabled;
            WindowSize           = _flowCrtlManager.StreamsInitialWindowSize;

            _flowCrtlManager.NewStreamOpenedHandler(this);
            OnFrameSent += (sender, args) => FramesSent++;
        }
예제 #3
0
        /// <summary>
        /// Creates stream.
        /// </summary>
        /// <param name="headers"></param>
        /// <param name="streamId"></param>
        /// <param name="priority"></param>
        /// <returns></returns>
        public Http2Stream CreateStream(HeadersList headers, int streamId, int priority = -1)
        {
            if (headers == null)
            {
                throw new ArgumentNullException("pairs is null");
            }

            if (priority == -1)
            {
                priority = Constants.DefaultStreamPriority;
            }

            if (priority < 0 || priority > Constants.MaxPriority)
            {
                throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority");
            }

            if (StreamDictionary.GetOpenedStreamsBy(_remoteEnd) + 1 > OurMaxConcurrentStreams)
            {
                throw new MaxConcurrentStreamsLimitException();
            }

            var streamSequence = new HeadersSequence(streamId, (new HeadersFrame(streamId, true)
            {
                Headers = headers
            }));

            _headersSequences.Add(streamSequence);

            var stream = StreamDictionary[streamId];

            stream.OnFrameSent += (o, args) =>
            {
                if (!(args.Frame is IHeadersFrame))
                {
                    return;
                }

                var streamSeq = _headersSequences.Find(stream.Id);
                streamSeq.AddHeaders(args.Frame as IHeadersFrame);
            };

            stream.OnClose += (o, args) =>
            {
                var streamSeq = _headersSequences.Find(stream.Id);

                if (streamSeq != null)
                {
                    _headersSequences.Remove(streamSeq);
                }
            };
            stream.Priority = priority;
            stream.Headers  = headers;
            stream.Opened   = true;

            return(stream);
        }
예제 #4
0
        /// <summary>
        /// Sends the headers with request headers.
        /// </summary>
        /// <param name="pairs">The header pairs.</param>
        /// <param name="priority">The stream priority.</param>
        /// <param name="isEndStream">True if initial headers+priority is also the final frame from endpoint.</param>
        public void SendRequest(HeadersList pairs, int priority, bool isEndStream)
        {
            if (_ourEnd == ConnectionEnd.Server)
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "Server should not initiate request");
            }

            if (pairs == null)
            {
                throw new ArgumentNullException("pairs is null");
            }

            if (priority < 0 || priority > Constants.MaxPriority)
            {
                throw new ArgumentOutOfRangeException("priority is not between 0 and MaxPriority");
            }

            var path = pairs.GetValue(CommonHeaders.Path);

            if (path == null)
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "Invalid request ex");
            }

            //09 -> 8.2.2.  Push Responses
            //Once a client receives a PUSH_PROMISE frame and chooses to accept the
            //pushed resource, the client SHOULD NOT issue any requests for the
            //promised resource until after the promised stream has closed.
            if (_promisedResources.ContainsValue(path))
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "Resource has been promised. Client should not request it.");
            }

            var stream = CreateStream(priority);

            stream.WriteHeadersFrame(pairs, isEndStream, true);

            var streamSequence = _headersSequences.Find(stream.Id);

            streamSequence.AddHeaders(new HeadersFrame(stream.Id, true)
            {
                Headers = pairs
            });

            if (OnRequestSent != null)
            {
                OnRequestSent(this, new RequestSentEventArgs(stream));
            }
        }
예제 #5
0
        //TODO Think about: writing push_promise is available in any time now. Need to handle it.
        public void WritePushPromise(IDictionary <string, string[]> pairs, Int32 promisedId)
        {
            if (Id % 2 != 0 && promisedId % 2 != 0)
            {
                throw new InvalidOperationException("Client cant send push_promise frames");
            }

            if (Closed)
            {
                return;
            }

            var headers = new HeadersList(pairs);
            var frame   = new PushPromiseFrame(Id, promisedId, true, true, headers);

            ReservedLocal = true;

            _writeQueue.WriteFrame(frame);

            if (OnFrameSent != null)
            {
                OnFrameSent(this, new FrameSentEventArgs(frame));
            }
        }
예제 #6
0
 //Incoming
 internal Http2Stream(HeadersList headers, int id,
                      WriteQueue writeQueue, FlowControlManager flowCrtlManager, int priority = Constants.DefaultStreamPriority)
     : this(id, writeQueue, flowCrtlManager, priority)
 {
     Headers = headers;
 }
예제 #7
0
        private void HandlePushPromiseFrame(PushPromiseFrame frame, out Http2Stream stream)
        {
            Http2Logger.LogDebug("PUSH_PROMISE frame: stream id={0}, payload len={1}, promised id={2}, " +
                                 "has pad={3}, pad high={4}, pad low={5}, end headers={6}",
                                 frame.StreamId, frame.PayloadLength, frame.PromisedStreamId, frame.HasPadding,
                                 frame.PadHigh, frame.PadLow, frame.IsEndHeaders);

            /* 12 -> 6.6.
             * PUSH_PROMISE frames MUST be associated with an existing, peer- initiated stream.
             * If the stream identifier field specifies the value 0x0, a recipient MUST respond
             * with a connection error of type PROTOCOL_ERROR. */
            if (frame.StreamId == 0)
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "push promise frame with stream id=0");
            }

            /* 12 -> 5.1
             * An endpoint that receives any frame after receiving a RST_STREAM MUST treat
             * that as a stream error of type STREAM_CLOSED. */
            if (StreamDictionary[frame.StreamId].Closed)
            {
                throw new Http2StreamNotFoundException(frame.StreamId);
            }


            //... a receiver MUST
            //treat the receipt of a PUSH_PROMISE that promises an illegal stream
            //identifier (Section 5.1.1) (that is, an identifier for a stream that
            //is not currently in the "idle" state) as a connection error
            //(Section 5.4.1) of type PROTOCOL_ERROR, unless the receiver recently
            //sent a RST_STREAM frame to cancel the associated stream (see
            //Section 5.1).
            if (frame.StreamId % 2 == 0 ||
                frame.PromisedStreamId == 0 ||
                (frame.PromisedStreamId % 2) != 0 ||
                frame.PromisedStreamId < _lastPromisedId ||
                !((StreamDictionary[frame.StreamId].Opened || StreamDictionary[frame.StreamId].HalfClosedLocal)))
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "Incorrect Promised Stream id");
            }

            var serializedHeaders = new byte[frame.CompressedHeaders.Count];

            Buffer.BlockCopy(frame.CompressedHeaders.Array,
                             frame.CompressedHeaders.Offset,
                             serializedHeaders, 0, serializedHeaders.Length);

            var decompressedHeaders = _comprProc.Decompress(serializedHeaders);
            var headers             = new HeadersList(decompressedHeaders);

            foreach (var header in headers)
            {
                Http2Logger.LogDebug("Stream {0} header: {1}={2}", frame.StreamId, header.Key, header.Value);

                frame.Headers.Add(header);
            }

            var sequence = _headersSequences.Find(frame.PromisedStreamId);

            if (sequence == null)
            {
                sequence = new HeadersSequence(frame.PromisedStreamId, frame);
                _headersSequences.Add(sequence);
            }
            else
            {
                //09 -> 6.6.  PUSH_PROMISE
                //A receiver MUST
                //treat the receipt of a PUSH_PROMISE on a stream that is neither
                //"open" nor "half-closed (local)" as a connection error
                //(Section 5.4.1) of type PROTOCOL_ERROR.

                //This means that we already got push_promise with the same PromisedId.
                //Hence Stream is in the reserved state.
                throw new ProtocolError(ResetStatusCode.ProtocolError,
                                        "Got multiple push promises with same Promised Stream id's");
            }

            //09 -> 6.6.  PUSH_PROMISE
            //A PUSH_PROMISE frame without the END_PUSH_PROMISE flag set MUST be
            //followed by a CONTINUATION frame for the same stream.  A receiver
            //MUST treat the receipt of any other type of frame or a frame on a
            //different stream as a connection error (Section 5.4.1) of type
            //PROTOCOL_ERROR.
            if (!sequence.IsComplete)
            {
                stream = null;
                return;
            }

            //09 -> 8.2.1.  Push Requests
            //The server MUST include a method in the ":method"
            //header field that is safe (see [HTTP-p2], Section 4.2.1).  If a
            //client receives a PUSH_PROMISE that does not include a complete and
            // valid set of header fields, or the ":method" header field identifies
            //a method that is not safe, it MUST respond with a stream error
            //(Section 5.4.2) of type PROTOCOL_ERROR.

            //Lets think that only GET method is safe for now
            var method = sequence.Headers.GetValue(CommonHeaders.Method);

            if (method == null || !method.Equals(Verbs.Get, StringComparison.OrdinalIgnoreCase))
            {
                var frameReceiveStream = GetStream(frame.StreamId);
                frameReceiveStream.WriteRst(ResetStatusCode.ProtocolError);
                frameReceiveStream.Close(ResetStatusCode.None);

                stream = null;
                return;
            }

            stream = GetStream(frame.PromisedStreamId);

            if (stream.Idle)
            {
                stream = CreateStream(sequence);
                stream.ReservedRemote = true;

                ValidateHeaders(stream);
                _lastPromisedId = stream.Id;
            }
            else
            {
                /* 12 -> 6.6
                 * Similarly, a receiver MUST treat the receipt of a PUSH_PROMISE that
                 * promises an illegal stream identifier (that is, an identifier for a stream that
                 * is not currently in the "idle" state) as a connection error of type PROTOCOL_ERROR. */
                throw new ProtocolError(ResetStatusCode.ProtocolError, "Remote endpoint tried to Promise incorrect Stream id");
            }
        }
예제 #8
0
        private void HandleHeaders(HeadersFrame headersFrame, out Http2Stream stream)
        {
            Http2Logger.LogDebug("HEADERS frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}, pad low={4}, " +
                                 "end stream={5}, has priority={6}, exclusive={7}, dependency={8}, weight={9}",
                                 headersFrame.StreamId, headersFrame.PayloadLength,
                                 headersFrame.HasPadding, headersFrame.PadHigh, headersFrame.PadLow, headersFrame.IsEndStream,
                                 headersFrame.HasPriority, headersFrame.Exclusive, headersFrame.StreamDependency, headersFrame.Weight);

            /* 12 -> 6.2
             * HEADERS frames MUST be associated with a stream.  If a HEADERS frame
             * is received whose stream identifier field is 0x0, the recipient MUST
             * respond with a connection error of type PROTOCOL_ERROR. */
            if (headersFrame.StreamId == 0)
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "Incoming headers frame with stream id=0");
            }

            var serializedHeaders = new byte[headersFrame.CompressedHeaders.Count];

            Buffer.BlockCopy(headersFrame.CompressedHeaders.Array,
                             headersFrame.CompressedHeaders.Offset,
                             serializedHeaders, 0, serializedHeaders.Length);

            var decompressedHeaders = _comprProc.Decompress(serializedHeaders);
            var headers             = new HeadersList(decompressedHeaders);

            foreach (var header in headers)
            {
                Http2Logger.LogDebug("{1}={2}", headersFrame.StreamId, header.Key, header.Value);
            }
            headersFrame.Headers.AddRange(headers);

            var sequence = _headersSequences.Find(headersFrame.StreamId);

            if (sequence == null)
            {
                sequence = new HeadersSequence(headersFrame.StreamId, headersFrame);
                _headersSequences.Add(sequence);
            }
            else
            {
                sequence.AddHeaders(headersFrame);
            }

            if (headersFrame.HasPriority)
            {
                //TODO: Priority was deprecated, now we need to use Dependency and Weight
                //sequence.Priority = headersFrame.Priority;
            }

            if (!sequence.IsComplete)
            {
                stream = null;
                return;
            }

            stream = GetStream(headersFrame.StreamId);
            if (stream.Idle)
            {
                stream = CreateStream(sequence);

                ValidateHeaders(stream);
            }
            else if (stream.ReservedRemote)
            {
                stream = CreateStream(sequence);
                stream.HalfClosedLocal = true;
                ValidateHeaders(stream);
            }
            else if (stream.Opened || stream.HalfClosedLocal)
            {
                stream.Headers = sequence.Headers;//Modify by the last accepted frame

                ValidateHeaders(stream);
            }
            else if (stream.HalfClosedRemote)
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "headers for half closed remote stream");
            }
            else
            {
                throw new Http2StreamNotFoundException(headersFrame.StreamId);
            }
        }
예제 #9
0
        private void HandleContinuation(ContinuationFrame contFrame, out Http2Stream stream)
        {
            Http2Logger.LogDebug("CONTINUATION frame: stream id={0}, payload len={1}, has pad={2}, pad high={3}," +
                                 " pad low={4}, end headers={5}", contFrame.StreamId, contFrame.PayloadLength,
                                 contFrame.HasPadding, contFrame.PadHigh, contFrame.PadLow, contFrame.IsEndHeaders);

            if (!(_lastFrame is ContinuationFrame || _lastFrame is HeadersFrame))
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError,
                                        "Last frame was not headers or continuation");
            }

            /* 12 -> 6.10
             * CONTINUATION frames MUST be associated with a stream. If a CONTINUATION
             * frame is received whose stream identifier field is 0x0, the recipient MUST
             * respond with a connection error of type PROTOCOL_ERROR. */
            if (contFrame.StreamId == 0)
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError,
                                        "Incoming continuation frame with stream id=0");
            }

            var serHeaders = new byte[contFrame.CompressedHeaders.Count];

            Buffer.BlockCopy(contFrame.CompressedHeaders.Array,
                             contFrame.CompressedHeaders.Offset,
                             serHeaders, 0, serHeaders.Length);

            var decomprHeaders = _comprProc.Decompress(serHeaders);
            var contHeaders    = new HeadersList(decomprHeaders);

            foreach (var header in contHeaders)
            {
                Http2Logger.LogDebug("Stream {0} header: {1}={2}", contFrame.StreamId, header.Key, header.Value);
            }
            contFrame.Headers.AddRange(contHeaders);
            var sequence = _headersSequences.Find(contFrame.StreamId);

            if (sequence == null)
            {
                sequence = new HeadersSequence(contFrame.StreamId, contFrame);
                _headersSequences.Add(sequence);
            }
            else
            {
                sequence.AddHeaders(contFrame);
            }

            if (!sequence.IsComplete)
            {
                stream = null;
                return;
            }

            stream = GetStream(contFrame.StreamId);
            if (stream.Idle || stream.ReservedRemote)
            {
                stream = CreateStream(sequence);

                ValidateHeaders(stream);
            }
            else if (stream.Opened || stream.HalfClosedLocal)
            {
                stream.Headers = sequence.Headers;//Modify by the last accepted frame

                ValidateHeaders(stream);
            }
            else if (stream.HalfClosedRemote)
            {
                throw new ProtocolError(ResetStatusCode.ProtocolError, "continuation for half closed remote stream");
            }
            else
            {
                throw new Http2StreamNotFoundException(contFrame.StreamId);
            }
        }